Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI modal presentation works only once from navigationBarItems

Tags:

ios

swift

swiftui

Here is a bug in SwiftUI when you show modal from button inside navigation bar items. In code below Button 1 works as expected, but Button 2 works only once:

struct DetailView: View {

    @Binding var isPresented: Bool
    @Environment (\.presentationMode) var presentationMode

    var body: some View {
        NavigationView {
            Text("OK")
            .navigationBarTitle("Details")
            .navigationBarItems(trailing: Button(action: {
                self.isPresented = false
                // or:
                // self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Done").bold()
            })
        }
    }
}

struct ContentView: View {

    @State var showSheetView = false

    var body: some View {
        NavigationView {
            Group {
                Text("Master")
                Button(action: { self.showSheetView.toggle() }) {
                    Text("Button 1")
                }
            }
            .navigationBarTitle("Main")
            .navigationBarItems(trailing: Button(action: {
                self.showSheetView.toggle()
            }) {
                Text("Button 2").bold()
            })
        }.sheet(isPresented: $showSheetView) {
            DetailView(isPresented: self.$showSheetView)
        }
    }
}

This bug is from the middle of the last year, and it still in Xcode 11.3.1 + iOS 13.3 Simulator and iOS 13.3.1 iPhone XS.

Is here any workaround to make button work?

EDIT:

  1. Seems to be tap area goes somewhere down and it's possible to tap below button to show modal.

Temporary solution to this is to use inline navigation bar mode: .navigationBarTitle("Main", displayMode: .inline)

like image 452
avdyushin Avatar asked Mar 02 '20 08:03

avdyushin


Video Answer


1 Answers

Well, the issue is in bad layout (seems broken constrains) of navigation bar button after sheet has closed

It is clearly visible in view hierarchy debug:

demo

Here is a fix (workaround of course, but safe, because even after issue be fixed it will continue working). The idea is not to fight with broken layout but just create another button, so layout engine itself remove old-bad button and add new one refreshing layout. The instrument for this is pretty known - use .id()

demo2

So modified code:

struct ContentView: View {

    @State var showSheetView = false
    @State private var navigationButtonID = UUID()
    
    var body: some View {
        NavigationView {
            Group {
                Text("Master")
                Button(action: { self.showSheetView.toggle() }) {
                    Text("Button 1")
                }
            }
            .navigationBarTitle("Main")
            .navigationBarItems(trailing: Button(action: {
                self.showSheetView.toggle()
            }) {
                Text("Button 2").bold() // recommend .padding(.vertical) here
            }
            .id(self.navigationButtonID)) // force new instance creation
        }
        .sheet(isPresented: $showSheetView) {
            DetailView(isPresented: self.$showSheetView)
                .onDisappear {
                    // update button id after sheet got closed
                    self.navigationButtonID = UUID()
                }
        }
    }
}
like image 171
Asperi Avatar answered Oct 18 '22 01:10

Asperi