1
votes

I have a custom SwiftUI title view, which consists of several Texts and an Image (the Image is left out for simplification). The SwiftUI view with the title view is being presented from a UIViewController, hence I need a custom back button that navigates back from the View (that's being displayed in a UIHostingController) to the previous UIViewController that presented it.

I want this custom back button to look exactly like the system back button, however, I cannot find the right system image for it. I've tried Image(systemName: "chevron.left") and also Text("❮") (along with pretty much all other system images and unicode arrow chars), but none of these provide the look of the system back button.

Is there any way to match the look of the system back button in SwiftUI?

This is what the system back button looks like (the trailing button is a Unicode left arrow, the leading button is the system back button): system back button

And my custom back button (as you can see, it's much smaller than the system one): enter image description here

TitleView.swift:

class TitleViewModel: ObservableObject {
    let backAction: () -> ()
    @Published var name: String
    @Published var subtitle: String?

    init(name: String, subtitle: String?, backAction: @escaping () -> ()) {
        self.name = name
        self.subtitle = subtitle
        self.backAction = backAction
    }
}

struct TitleView: ViewModifier {
    @ObservedObject var viewModel: TitleViewModel

    func body(content: Content) -> some View {
        ZStack(alignment: .top) {
            NavigationView {
                VStack {
                    content
                        .navigationBarTitle("", displayMode: .inline)
                        .navigationBarItems(leading: backButton)
                    NavigationLink(destination: Text("Destination").navigationBarItems(trailing: Text("❮")), label: { Text("Next")
                    })
                }
            }
            titleView
        }
    }

    private var backButton: some View {
        Button(action: viewModel.backAction) { Image(systemName: "chevron.left") }
    }

    private var titleView: some View {
        VStack {
            Text(viewModel.name)
            Text(viewModel.subtitle ?? "")
        }
    }
}

extension View {
    func titleView(_ viewModel: TitleViewModel) -> some View {
        modifier(TitleView(viewModel: viewModel))
    }
}

DetailsView.swift

public struct DetailsView: View {
    public var backAction: () -> ()

    public var body: some View {
        Text("Text")
            .titleView(TitleViewModel(name: "Title", subtitle: "SubTitle", backAction: backAction))
    }
}

public class DetailsViewController: UIHostingController<DetailsView> {
    public override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(true, animated: false)
    }
}

public struct RootView: View {
    public var next: () -> ()

    public var body: some View {
        VStack {
            Text("Root")
            Button(action: next, label: { Text("Next") })
        }
    }
}

And in SceneDelegate, set up a UINavigationController that presents a UIHostingController as the rootViewController

func instantiateRootViewController() -> UIViewController {
    let navigationController = UINavigationController()
    let detailsView = DetailsView(backAction: { navigationController.popViewController(animated: true) })
    let presentDetailsView = { navigationController.pushViewController(DetailsViewController(rootView: detailsView), animated: true) }
    let rootViewController = UIHostingController(rootView: RootView(next: presentDetailsView))
    navigationController.pushViewController(rootViewController, animated: true)
    return navigationController
}

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        let rootViewController = instantiateRootViewController()
        window.rootViewController = rootViewController
        self.window = window
        window.makeKeyAndVisible()
    }
}
1

1 Answers

0
votes

You may try the following:

private var backButton: some View {
    Button(action: {}) {
        Image(systemName: "chevron.left")
            .scaleEffect(0.83)
            .font(Font.title.weight(.medium))
    }
}

Optionally you can apply .offset as well, but this may not adapt properly for larger accessibility font sizes:

.offset(x: -7, y: 0)