Is there still no simple way to customize the Back-button in the NavigationBar in SwiftUI?

1.6k Views Asked by At

You can now use the .toolbar-modifier to set the NavigationBar principal content as you please:

.toolbar {
    ToolbarItem(placement: .principal) {
        Text("Custom Title")
            .font(.title)
    }
}

Is there an equally simple way to customize the Back-button (text and/or whole button) that does not involve hiding the default button and creating your own, which then also requires recreating the correct functionality (ie. actually going back and enabling swipe to go back)?

2

There are 2 best solutions below

2
On

Use UIButton().

struct ToolbarButton: UIViewRepresentable {

    private let sfSymbolName: String
    
    private let titleColor: UIColor
    
    private let action: () -> ()
    
    internal init(sfSymbolName: String, titleColor: UIColor, action: @escaping () -> ()) {
        self.sfSymbolName = sfSymbolName
        self.titleColor = titleColor
        self.action = action
    }

    
    func makeUIView(context: Context) -> UIButton {
        let button = UIButton()
        let largeConfig = UIImage.SymbolConfiguration(scale: .large)


        // Use custom icon instead of system icons.

        let image = UIImage(systemName: sfSymbolName, withConfiguration: largeConfig)
        button.setImage(image, for: .normal)
        button.tintColor = titleColor
        button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
        button.addTarget(context.coordinator, action: #selector(context.coordinator.didTapButton(_:)), for: .touchUpInside)
        return button
    }
    
    func updateUIView(_ uiView: UIButton, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(action: action)
    }
    
    
    class Coordinator {
        private let action: () -> ()
        
        init(action: @escaping () -> ()) {
            self.action = action
        }
        
        @objc
        func didTapButton(_ sender: UIButton) {
            self.action()
        }
    }
}

struct CloseButton: View {
    var onClose: () -> ()
    var spacing: CGFloat

    init(spacing: CGFloat = 2, onClose: @escaping () -> ()) {
        self.spacing = spacing
        self.onClose = onClose
    }
    
    var body: some View {
        ToolbarButton(sfSymbolName: "plus", titleColor: UIColor(Color.accentColor), action: self.onClose)
            .rotationEffect(.degrees(45), anchor: .center)
            .padding(2)
            .background(Circle().fill(Color.systemGray2))
            .padding(2)
            .animation(.easeOut)
    }
}
0
On

Equally as simple no there is no way to do it. SwiftUI is for basic coding, it is a starter package.

But if you tap into UIKit where the magic really happens it doesn't get any simpler than picking what you want to modify and telling it what you want it to be.

The code below is for the navigation bar in general, background, title, back button image, back button title, etc. It affects your entire App. It is not complete there are some quicks to it but you should get a decent picture on how to make it your own.

struct HomeView: View{
    @State var ispresented = true
    var body: some View {
        ZStack {
            NavigationView {
                NavigationLink(
                    destination: ListView(),
                    isActive: $ispresented,
                    label: {
                        Text("List View")
                    }).navigationTitle("Home")
            }
        }
    }
}
struct ListView: View{
    init() {
    }
    var body: some View {
        ZStack {
            List{
                Text("List")
            }.navigationTitle("Sample")
            
        }
    }
}
struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}
extension UINavigationController {
    
    override open func viewDidLoad() {
        super.viewDidLoad()
        
        //.inline
        let standard = navigationBar.standardAppearance
        standard.backgroundColor = .blue
        standard.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.yellow,NSAttributedString.Key.font: UIFont(name: UIFont.familyNames[4], size: 20)!]
        //This one is for standard and compact
        standard.setBackIndicatorImage(UIImage(systemName: "checkmark"), transitionMaskImage: UIImage(systemName: "checkmark"))
        
        //Landscape
        let compact = navigationBar.compactAppearance
        compact?.backgroundColor = .blue
        compact?.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.red]
        
        //.large
        let scrollEdge = navigationBar.standardAppearance
        //This image overrides standard and compact
        scrollEdge.setBackIndicatorImage(UIImage(systemName: "plus"), transitionMaskImage: UIImage(systemName: "plus"))
        
        scrollEdge.backgroundColor = .blue
        
        scrollEdge.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.green,NSAttributedString.Key.font: UIFont(name: UIFont.familyNames[2], size: 48)!]
        
        scrollEdge.backButtonAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.magenta]
        
        navigationBar.standardAppearance = standard
        navigationBar.compactAppearance = compact
        navigationBar.scrollEdgeAppearance = scrollEdge
        
        //This color the Back Button Image
        navigationBar.tintColor = .brown
        
    }
}