I have a custom SwiftUI title view, which consists of several Text
s 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):
And my custom back button (as you can see, it's much smaller than the system one):
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()
}
}