Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI Sheets: Inconsistent sheet behaviour with navigationBarItems

Tags:

ios

swift

swiftui

Xcode: Version 11.3 (11C29)
Target: Simulator running iOS 13.3

I'm getting inconsistent behaviour that's hard to diagnose when working with sheets and SwiftUI. When I:
* Use a button in the NavigationView itself, I can consistently call up the sheet and dismiss it
* Use a button in the bar of the NavigationView (i.e. navigationBarItems), the behaviour is inconsistent: sometimes it toggles without issue, sometimes it will "lock up" and not respond for ~10 seconds before functioning again. Swiping around and performing other actions in the interface seem to help in "resetting" the functionality.

Minimum reproducible example:

import SwiftUI

struct ContentView: View {
    @State var isModalShowing = false

    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(1..<5) { number in
                        Text(String(number))
                    }
                }
                Button("First Modal Button") {
                    self.isModalShowing = true
                }
            }
            .navigationBarItems(leading:
                Button("Second Modal Button") {
                    self.isModalShowing = true
            })
        }
        .sheet(isPresented: $isModalShowing) {
            TestView(isPresented: self.$isModalShowing)
        }
    }
}

struct TestView: View {
    @Binding var isPresented: Bool
    var body: some View {
        VStack {
            Text("Hello SwiftUI!")
            Button("Dismiss") {
                self.isPresented = false
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

To note:
* Pressing First modal button works consistently
* Pressing Second modal button works inconsistently after dismissing the modal for the first time (try using this button ~5-10 times in a row, it should lock up, but it's hard to predict when it will)

There's already a few StackOverflow questions/answers about this, but none have worked for me so far and they seem to talk about the Xcode Betas, whereas I am on stable (hopefully 11.3 is stable). Different ways of passing the state (@Binding vs @Environment) didn't help. Moving around where the .sheet is called also didn't change anything. I think it's also related to the List and/or ForEach but I'm new to Swift and not sure how to debug further.

like image 436
d00bsm3n Avatar asked Dec 26 '19 23:12

d00bsm3n


1 Answers

I believe this is a bug. Though my answer isn't going to solve this problem, but can give you options to consider.

Out of curiosity, I did some debugging of the view hierarchy. With the current setup, initially the navigation bar button stays here (highlighted as blueish color):

enter image description here

Then you present the sheet and do the dismissal of the sheet accordingly. After the dismissal, the position of the navigation bar button isn't quite right. See:

enter image description here

The Text of the button stays in the right position, but the bar button is displaced from where it should be.

But this displacement doesn't happen if you use NavigationLink instead of .sheet presentation.

Well, this issue is prevalent for the large option of TitleDisplayMode of navigation bar title, and it's the default for a quite long. But if the inline option is used, the issue doesn't exist. See, with the inline option, before and after the sheet dismissal the navigation bar button stays at the same place:

enter image description here


So that means, you now have two options to consider:

  1. Use NavigationLink instead of sheet presentation. You can do this by placing the below code inside the outermost View (in your case the VStack):

    NavigationLink(destination: TestView(), isActive: self.$isModalShowing) {
        EmptyView()
        // no need to provide any other view as it will be triggered by the action
        // of navigation bar button item which already provides its own view
    }
    

Note: This NavigationLink option isn't tested with Xcode 11.3 or newer as navigation link seems to misbehave with this version. But works as expected upto Xcode 11.2.1. More on SwiftUI unable to navigate back and forth with navigationLink

  1. Use the inline option of the navigation bar TitleDisplayMode as:

    .navigationBarTitle(Text("Home"), displayMode: .inline)
    // you can get rid of the title "Home" with empty "" string though
    
like image 194
nayem Avatar answered Oct 17 '22 04:10

nayem