Search code examples
iosswiftswiftuiswiftui-navigationstack

Inconsistent spacing between navigation bar toolbar items in SwiftUI


I have 2 trailing navigation bar buttons called Draw and Save.

On tapping Draw, I want 2 additional toolbar buttons to be added to the navigation bar.

This is how I am adding my nav bar buttons:

var body: some View {
    ZStack {
        PDFRepresentedView(drawingMode: $pdfViewerViewModel.drawingMode,
                           viewState: $pdfViewerViewModel.pdfViewState,
                           url: pdfViewerViewModel.pdfFileURL)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                nextPageNavBarItem
            }
            
            ToolbarItem(placement: .navigationBarTrailing) {
                previousPageNavBarItem
            }
            
            ToolbarItem(placement: .navigationBarTrailing) {
                drawNavBarItem
            }
            
            ToolbarItem(placement: .navigationBarTrailing) {
                saveNavBarItem
                    .bold()
            }
            
            ToolbarItem(placement: .navigationBarLeading) {
                cancelNavBarTitle
                    .alert(isPresented: $shouldShowCancelAlert) {
                        Alert(title: Text(pdfViewerViewModel.localizedText(for: .warningAlertTitle)),
                              message: Text(pdfViewerViewModel.localizedText(for: .cancelWarningMessage)),
                              primaryButton: .cancel(Text(pdfViewerViewModel.localizedText(for: .stayButtonTitle))),
                              secondaryButton: .destructive(Text(pdfViewerViewModel.localizedText(for: .leaveButtonTitle))) {
                            pdfViewerViewModel.shouldSaveData = false
                            dismiss()
                        })
                    }
            }
        }
    }
}

These are the buttons:

private var saveNavBarItem: some View {
    return AnyView(Button(action: {
        pdfViewerViewModel.shouldSaveData = false
        dismiss()
    }, label: {
        Text(pdfViewerViewModel.localizedText(for: .saveButtonTitle))
    }))
}

@ViewBuilder
private var drawNavBarItem: some View {
    Button(action: {
        pdfViewerViewModel.drawingMode.toggle()
    }, label: {
        Image(systemName: pdfViewerViewModel.drawingIconName())
    })
}

@ViewBuilder
private var nextPageNavBarItem: some View {
    if pdfViewerViewModel.drawingMode {
        Button(action: {
            pdfViewerViewModel.pdfViewState = .nextPage
        }, label: {
            Image(systemName: pdfViewerViewModel.nextPageIconName())
        })
    }
}

@ViewBuilder
private var previousPageNavBarItem: some View {
    if pdfViewerViewModel.drawingMode {
        Button(action: {
            pdfViewerViewModel.pdfViewState = .previousPage
        }, label: {
            Image(systemName: pdfViewerViewModel.previousPageIconName())
        })
    }
}

So it starts out like this:

Navigation view, Navigation stack bar button items swiftUI

Then when I tap draw, the 2 other buttons get added, however, the spacing as you see is off between the draw and the previous (up arrow) button:

SwiftUI Navigation Toolbar buttons

If those 2 buttons are there from the beginning without any show / hide logic / binding, the spacing seems to be consistent:

Toolbar Navigation bar buttons SwiftUI

How can I achieve standard spacing when adding buttons to the bar button item at run time


Solution

  • I'm not able to reproduce this problem, the gaps are consistent when I toggle the flag that controls their visibility. But a workaround might be to show a hidden placeholder in the position where a button will be shown. Something like:

    private func buttonPlaceholder(systemName: String) -> some View {
        Button {} label: {
            Image(systemName: systemName)
        }
        .hidden()
        .accessibilityHidden(true)
    }
    
    @ViewBuilder
    private var nextPageNavBarItem: some View {
        if pdfViewerViewModel.drawingMode {
            Button(action: {
                pdfViewerViewModel.pdfViewState = .nextPage
            }, label: {
                Image(systemName: pdfViewerViewModel.nextPageIconName())
            })
        } else {
            buttonPlaceholder(systemName: pdfViewerViewModel.nextPageIconName())
        }
    }