50

I have just started using SwiftUI a couple of weeks ago and i'm still learning. Today I ran into an issue.

When I present a sheet with a navigationBarItems-button and then dismiss the ModalView and return to the ContentView I find myself unable to click on the navigationBarItems-button again.

My code is as follows:

struct ContentView: View {
    
    @State var showSheet = false
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Test")
            }.sheet(isPresented: self.$showSheet) {
                ModalView()
            }.navigationBarItems(trailing:
                Button(action: {
                    self.showSheet = true
                }) {
                    Text("SecondView")
                }
            )
        }
    }
}

struct ModalView: View {
    
    @Environment(\.presentationMode) var presentation
    
    var body: some View {
        VStack {
            Button(action: {
                self.presentation.wrappedValue.dismiss()
            }) {
                Text("Dismiss")
            }
        }
    }
}
Markus Moltke
  • 970
  • 1
  • 8
  • 20
  • Markus, have you checked my answer and did it work for you? – Vahagn Gevorgyan Jun 19 '20 at 20:25
  • 1
    Wow! This still exists on the first day of WWDC 2021. Maybe the next editiion. – David Jun 07 '21 at 15:32
  • Still an issue for me in Xcode 13.2.1 iOS 15.2 smh – shim Jan 03 '22 at 04:07
  • For whatever reason, this bug is only present (for me) in the iPhone 14 simulator, but not in others. After pulling my hair out for several hours, I tried a different simulator target on a whim (iPhone 14 Pro) and everything worked as expected. No bug after sheet dismiss. WTF. Tried SE (3rd gen), no bug. 14 Pro Max, no bug. All running iOS 16. What is going on here? Anyone else tried other simulator targets? EDIT: Used the "Erase content and settings" option for the 14 simulator. Bug is gone. I lost a good two hours of my life on this one. I just don't know what to think anymore. – Sam Sep 26 '22 at 15:32

8 Answers8

64

I think this happens because the presentationMode is not inherited from the presenter view, so the presenter didn't know that the modal is already closed. You can fix this by adding presentationMode to presenter, in this case to ContentView.

struct ContentView: View {

    @Environment(\.presentationMode) var presentation
    @State var showSheet = false

    var body: some View {
        NavigationView {
            VStack {
                Text("Test")
            }.sheet(isPresented: self.$showSheet) {
                ModalView()
            }.navigationBarItems(trailing:
                Button(action: {
                    self.showSheet = true
                }) {
                    Text("SecondView")
                }
            )
        }
    }
}

Tested on Xcode 12.5.

Here is the full working example.

Vahagn Gevorgyan
  • 2,635
  • 19
  • 25
  • 2
    Works for me too - I don't think this is intended behaviour from Apple, but this is a good workaround until they fix it. This should be the accepted answer. – podomunro May 03 '20 at 13:21
  • 1
    Very nice. I was also struggling with not able to click on plus button after initial pressing. This solution worked for me. Thanks – Ishwar Jindal Jul 08 '20 at 10:56
  • 3
    Am I the only one that this doesn't work for? Some taps just don't get registered. I'm still convinced this is a SwiftUI bug, and I had an Apple engineer verbally agree in a WWDC20 labs session. I'm desperate for a workaround. – Jeremy Jul 09 '20 at 19:33
  • @Jeremy here is the full working example, you can just copy-past it into new project and it will work. https://gist.github.com/gevorgyanvahagn/3f60543a8a5ed5a40edabe00a2d770da – Vahagn Gevorgyan Jul 10 '20 at 22:30
  • 1
    I now realize we are talking about two different bugs. With that exact code, I still have the same problem as @Ralf Ebert - many times, the tap on "SecondView" is not registered. I believe this to be a fundamental SwiftUI bug. For this particular code, commented out the presentationMode line does not change anything for me. In both cases, it presents the sheet with or without that line, and the button is always unresponsive. – Jeremy Jul 13 '20 at 15:17
  • 1
    Saved my day. Thanks. Issue still present in Xcode 12.5 – kluver Jul 05 '21 at 12:46
  • 1
    This doesn't seem to work for me, The only way I could get it working was to change it from presenting as a sheet to fullScreenCover – Mohammed Shakeer Oct 15 '22 at 10:44
  • @MohammedShakeer it should work, can you provide minimal code for reproducing your issue? – Vahagn Gevorgyan Oct 15 '22 at 15:32
  • @VahagnGevorgyan code was too long to paste here, posted a new question https://stackoverflow.com/questions/74092974/swiftui-navigation-bar-items-frame-are-misaligned-after-sheet-dismiss – Mohammed Shakeer Oct 17 '22 at 05:45
16

This seems to be a bug in SwiftUI. I am also still seeing this issue with Xcode 11.5 / iOS 13.5.1. The navigationBarMode didn't make a difference.

I filed an issue with Apple:


FB7641003 - Taps on a navigationBarItem Button presenting a sheet sometimes not recognized

You can use the attached example project SwiftUISheet (also available via https://github.com/ralfebert/SwiftUISheet) to reproduce the issue. It just presents a sheet from a navigation bar button. Run the app and tap repeatedly on the 'plus' button in the nav bar. When the sheet pops up, dismiss it by sliding it down. Only some taps to the button will be handled, often a tap is ignored.

Tested on Xcode 11.4 (11E146) with iOS 13.4 (17E255).

Ralf Ebert
  • 3,556
  • 3
  • 29
  • 43
  • Any update on this? From what I have experienced, if I swipe down to dismiss the View I can tap the button and bring it back up again. But if I tap the 'Done' button on my sheet to dismiss, then the other navigation item, the +, does not work. – Luke Irvin Apr 06 '20 at 15:41
  • @LukeIrvin have you tried the solution I've offered? – Vahagn Gevorgyan Apr 29 '20 at 11:28
  • Got an update on the FB to re-test on iOS 13.5 Beta 4 (I currently have no device on the beta, just wanted to keep you updated) – Ralf Ebert May 11 '20 at 10:27
  • I'm experiencing the same thing, still on 13.5.1 and Xcode 11.5 (11E608c). I seem to have the opposite problem of @LukeIrvin, if I tap my 'Close' button it's much more reliable to present the modal next time, but swiping down makes it worse. I occasionally experience problems either way though. – Jeremy Jun 08 '20 at 20:16
  • 1
    This is still reproducible on iOS 13.5.1 and iOS 14 Beta 1. – Ralf Ebert Jun 27 '20 at 20:58
  • 4
    I had a WWDC Labs session with an Apple Engineer focussed on this bug. The engineer seemed to verbally acknowledge that this seemed like a bug, and he was noticing the same thing on his device. There has been no reply or followups to my Feedback (which mentioned FB7641003). – Jeremy Jun 29 '20 at 16:52
6

Very hacky but this worked for me:

Button(action: {
    self.showSheet = true
}) {
    Text("SecondView")
    .frame(height: 96, alignment: .trailing)
}
adamwjohnson5
  • 663
  • 7
  • 11
5

I'm still seeing this issue with Xcode 13 RC and iOS 15. Unfortunately the solutions above didn't work for me. What I ended up doing is adding a small Text view to the toolbar whose content changes depending on the value of the .showingSheet property.

struct ContentView: View {
    @State private var showingSheet = false

    var body: some View {
        NavigationView {
            VStack {
                Text("Content view")
                Text("Swift UI")
            }
            .sheet(isPresented: $showingSheet) {
                Text("This is a sheet")
            }
            .navigationTitle("Example")
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    // Text view workaround for SwiftUI bug
                    // Keep toolbar items tappable after dismissing sheet
                    Text(showingSheet ? " " : "").hidden()
                    Button(action: {
                        self.showingSheet = true
                    }) {
                        Label("Show Sheet", systemImage: "plus.square")
                    }
                }
            }
        }
    }
}

I realize it's not ideal but it's the first thing that worked for me. My guess is that having the Text view's content change depending on the .showingSheet property forces SwiftUI to fully refresh the toolbar group.

joltguy
  • 536
  • 5
  • 6
  • 1
    This is the only solution that works for me in Xcode 14 (the bug is still here). Thanks! – Josh Sep 15 '22 at 01:04
1

So far, I can still observe the disorder of navi buttons right after dismissing its presented sheet.

FYI, I am using a UINavigationController wrapper instead as workaround. It works well.

Unfortunately, I sure that the more that kind of bugs, the farther away the time of using SwiftUI widely by the ios dev guys. Because those are too basic to ignore.

Kyokook Hwang
  • 2,682
  • 21
  • 36
  • Do you have a link to how to use a UINavigationController wrapper as a workaround? I'd like to try that. – Jeremy Jul 09 '20 at 19:37
1

Very hacky but this worked for me:

I had the same problem. this solution worked for me.

struct ContentView: View {

    @State var showSheet = false

    var body: some View {
        NavigationView {
            VStack {
                Text("Test")
            }.sheet(isPresented: self.$showSheet) {
                ModalView()
            }.navigationBarItems(trailing:
                Button(action: {
                    self.showSheet = true
                }) {
                    Text("SecondView")
                        // this is a workaround
                        .frame(height: 96, alignment: .trailing)
                }
            )
        }
    }
}
glassonion1
  • 29
  • 1
  • 4
1

Only @adamwjohnson5's answer worked for me. I don't like doing it but it's the only solution that works as of Xcode 13.1 and iOS 15.0. Here is my code for anyone interested in seeing iOS 15.0 targeted code:

var body: some View {
    NavigationView {
        mainContentView
            .navigationTitle(viewModel.navigationTitle)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    PlusButton {
                        viewModel.showAddDialog.toggle()
                    }
                    .frame(height: 96, alignment: .trailing) // Workaroud, credit: https://stackoverflow.com/a/62209223/5421557
                    .confirmationDialog("CatalogView.Add.DialogTitle", isPresented: $viewModel.showAddDialog, titleVisibility: .visible) {
                        Button("Program") {
                            viewModel.navigateToAddProgramView.toggle()
                        }
                        
                        Button("Exercise") {
                            viewModel.navigateToAddExerciseView.toggle()
                        }
                    }
                }
            }
            .sheet(isPresented: $viewModel.navigateToAddProgramView, onDismiss: nil) {
                Text("Add Program View")
            }
            .sheet(isPresented: $viewModel.navigateToAddExerciseView, onDismiss: nil) {
                AddEditExerciseView(viewModel: AddEditExerciseViewModel())
            }
    }
    .navigationViewStyle(.stack)
}
Nirvan Nagar
  • 189
  • 3
  • 11
0

The core issue is misaligned navigation bar item frame. The safe solution would be to force bar items to be recreated each time a modal closes. Yes, it's inefficient to recreate views, but since bar buttons are quite simple, such an overhead doesn't seem noticeable.

So, continuing a flash mob of finding their own workarounds, here's mine:

  1. In the view, place:
@Environment(\.presentationMode) var presentationMode

that would force the view to be updated each time a modal is presented or dismissed from it;

  1. Declare toolbar items with random IDs
ToolbarItem(id: UUID().uuidString, placement: .navigationBarLeading) { ... }

that way, they are recreated each time the view is updated (because of the new ID randomly generated each time)