swiftswiftuipositiontoolbartoolbaritems

SwiftUI: how to present more than one ToolbarItem using semantic placement?


The WWDC 2020 SwiftUI talks made mention of a new ".toolbar" modifier. Toolbars vary depending on the device being used and the toolbar modifier should create the proper type of toolbar for the device. Moreover, toolbars vary depending on how a view gets displayed - i.e. did you navigate to the scene or was the scene modally presented?

In the playground code, below, there are two View structs. The first is called MainToolbarView and gets displayed (I believe) as if the playground had navigated to it. The second is called PresentedView and is presented modally from MainToolbarView.

MainToobarView (non-modal)

In the MainToolbarView the toolbar has positional placement for its Save and Cancel buttons (.navigationBarLeading and .navigationBarTrailing). This works fine. Tapping either button causes a text field to display either "Save" or "Cancel".

PresentedView (modal)

When the Present Sheet button is tapped the second view is modally presented. There is a problem in toolbar for the presented sheet. The toolbar is using semantic placement for its Confirm and Deny buttons (.confirmationAction and .cancellationAction). However, only the Confirm button is displayed.

Can anyone tell me why both buttons are shown on the MainToolbarView but only one button is on the PresentedView toolbar? I've tried changing the order of the buttons, making the two buttons into an HStack, or even trying to use positional placement. None of those options have worked for me.

import SwiftUI
import PlaygroundSupport

struct MainToolbarView: View {

    @State private var buttonIdentifier = "initial state"
    @State private var showSheet = false

    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                Text( $buttonIdentifier.wrappedValue )
                Spacer()
                Button( "Present Sheet" )
                    { showSheet = true }
                Spacer()
            }
            .toolbar {
                ToolbarItem( placement: .navigationBarLeading )
                    { Button( "Save", action: reportSave ) }
                ToolbarItem( placement: .navigationBarTrailing )
                    { Button( "Cancel", action: reportCancel ) }
            }
            .navigationBarTitle( "MAIN" )
        }
        .sheet(isPresented: $showSheet )
            { PresentedView(buttonIdentifier: $buttonIdentifier ) }
    }
    private func reportSave()
    {
        buttonIdentifier = "save"
    }

    private func reportCancel()
    {
        buttonIdentifier = "cancel"
    }
}


struct PresentedView: View {

    // MARK: API
    @Binding var buttonIdentifier: String

    // MARK: instance variables
    @Environment( \.presentationMode ) var mode

    //.confirmationAction

    var body: some View {
        NavigationView {
            Text( "Tap action buttons" )
                .toolbar {
                    ToolbarItem( placement: .confirmationAction )
                        { Button( "Confirm", action: reportConfirm ) }
                    ToolbarItem( placement: .cancellationAction )
                        { Button( "Deny", action: reportDenied ) }
                }
                .navigationBarTitle( "SHEET" )
        }
    }

    private func reportDenied() {
        buttonIdentifier = "sheet denied"
        self.mode.wrappedValue.dismiss()
    }

    private func reportConfirm() {
        buttonIdentifier = "sheet confirmed"
        self.mode.wrappedValue.dismiss()
    }
}


 PlaygroundPage.current.setLiveView( MainToolbarView() )

`


Solution

  • I think the trick (and for me is a bug) is using the .navigationBarBackButtonHidden(true) modifier. After using it (add it below .navigationBarTitle( "SHEET" )), I see both buttons.