swiftuinavigationbartabviewswiftui-navigationstacknavigationbaritems

Re-using same code for navigationbar items in SwiftUI


I'm setting up an SwiftUI app with TabView-navigation and individual NavigationStacks (to preserve individual navigation state per stack).

One thing that buggles my mind is that I want to have the same profile button in the top right navigationbar on every View. Adding the navigationbar item as below requires me to have the exact same code/addition in every View.

.navigationDestination(isPresented: $presentProfilePage) {
    ProfileView()
}
.toolbar {
    Button {
        presentProfilePage = true
    } label: {
        if let img = user.displayImg {
            Image(uiImage: img)
                .resizable()
                .frame(width: 48, height: 48)
        } else {
            Image.defaultProfileImage
        }
    }
}

Is there a way to set the navigationbar items once and then they are visible on all views?


Solution

  • You can put this code in a ViewModifier. You can put presentProfilePage as a @State in there.

    struct ProfileNavigation: ViewModifier {
        @State var presentProfilePage = false
        let image: UIImage?
        
        func body(content: Content) -> some View {
            content
                .navigationDestination(isPresented: $presentProfilePage) {
                    ProfileView()
                }
                .toolbar {
                    Button {
                        presentProfilePage = true
                    } label: {
                        if let image {
                            Image(uiImage: image)
                                .resizable()
                                .frame(width: 48, height: 48)
                        } else {
                            Image.defaultProfileImage
                        }
                    }
                }
        }
    }
    
    extension View {
        func profileNavigation(image: UIImage?) -> some View {
            modifier(ProfileNavigation(image: image))
        }
    }
    

    You'd just add .profileNavigation(...) to each view you want to have this button.

    Alternatively, you can use a NavigationLink in the toolbar directly. You would just extract the NavigationLink as a View to reuse it:

    struct ProfileNavigation: View {
        let image: UIImage?
        
        var body: some View {
            NavigationLink {
                ProfileView()
            } label: {
                if let image {
                    Image(uiImage: image)
                        .resizable()
                        .frame(width: 48, height: 48)
                } else {
                    Image.defaultProfileImage
                }
            }
        }
    }
    

    Usage:

    .toolbar {
        ProfileNavigation(...)
    }