12

Hello I am running into a problem here and I do not have a consistent behavior between my .sheet() view when running on ios13 or ios14

I got a view like this :

@State private var label: String = "" 
@State private var sheetDisplayed = false
///Some code
var body: some View {
   VStack {
      Button(action: {
         self.label = "A label"
         self.isDisplayed = true
      }) {
           Text("test")
       }
   }.sheet(isPresented: $sheetDisplayed, onDismiss: {
        self.label = ""
    }) {
        Text(self.label)
       }
 }

On ios 13 this work as expected btn click -> set label -> call sheet -> display "A label" in a Text view.

On ios14 I got an empty string in self.label when in sheet closure, hence it does not display anything.

Did I missed something ? Is it an iOS 14 bug or did I had it wrong on ios13 and that got corrected.

PS: I have a couple of other variables that are passed in the closure I simplified it.

FitzChill
  • 815
  • 8
  • 18
  • It looks like the sheet is created before you set the label. You shouldn't rely on the order in which SwiftUI creates views but in this case I believe it is a bug worth submitting to Apple. – pawello2222 Sep 17 '20 at 22:47
  • Same thing happening in iOS 15. – Rob N Sep 28 '21 at 22:08
  • Thanks for the comment. If you encounter the problem check the accepted answer I still works on ios 15 – FitzChill Sep 30 '21 at 06:46

3 Answers3

11

Your code have expectation of view update/creation order, but in general it is undefined (and probably changed in iOS 14).

There is explicit way to pass information inside sheet - use different sheet creator, ie. .sheet(item:...

Here is working reliable example. Tested with Xcode 12 / iOS 14

struct ContentView: View {
    @State private var item: Item?

    struct Item: Identifiable {
        let id = UUID()
        var label: String = ""
    }

    var body: some View {
        VStack {
            Button(action: {
                self.item = Item(label: "A label")
            }) {
                Text("test")
            }
        }.sheet(item: $item, onDismiss: {
            self.item = nil
        }) {
            Text($0.label)
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • I was able to make it work using your solution Thanks. But still it is strange that when adding didSet prop observers and printing values (or in debug) my value does changes before going into that sheet content closure and still have nil in the sheet. – FitzChill Sep 18 '20 at 09:27
4

This is some really strange behaviour in iOS 14, which doesn't appear to be documented.

Using the other answer here and the comment on this thread, I used @Binding to solve the issue as it seemed the cleanest and most SwiftUI-esq solution.

I have no idea why this behaviour has changed, and it seems less intuitive than before, so I'm assuming its a bug!

An example:

struct MainView: View {
    @State private var message = ""
    @State private var showSheet = false

    var body: some View {
        Button(action: {
            self.message = "This will display the correct message"
            self.showSheet = true
        }, label: {
            Text("Test Button")
        })
        .sheet(isPresented: self.$showSheet) {
            SheetView(message: self.$message)
        }
    }
}

struct SheetView: View {
    @Binding var message: Int

    var body: some View {
        Text(self.message)
    }
}
Othyn
  • 829
  • 1
  • 9
  • 21
  • 1
    Thank you for the update, it's been a couple of days that I haven't looked at that thread but I remember having tryed this with no succes (at least in my case, I over simplified my problem here) But I hat an optionnal that was nil in my sheet closure, making the app crash. – FitzChill Oct 05 '20 at 09:04
  • 1
    Can you please explain me, why do you need to pass `Binding`? Why passing just a wrapped value doesn't work? I tested the solution in my code and adding magic `$` and unwrapping the value later just works... – rraszewski Oct 07 '20 at 11:09
  • 1
    I guess because the sheet is able to modify the value that is binded so it need to be a binding as a normal value/reference object will be lost in the parent view. Moreover it's required by the sheet() constructor (eather a isPresented: Binding or the item:Binding see here for reference https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:) – FitzChill Oct 07 '20 at 12:45
0

The behaviour changed with SwiftUI 2.0, so it affects MacOS 11 as well, just adding a binding to the view fixes it even when that binding is never used, which makes me think this is an implementation bug. Additionally just using the details state variable in a Text() within the body of the view also fixes it.

struct MyViewController : View {

    @State var details: String?
    @State var showDetails = false

    //    @Binding var havingAbindingFixesIt: String?

    var body: some View {
        VStack {
            //            Text(details ?? "")
            Text("Tap here for details")
                .onTapGesture {
                    self.details = "These are the details"
                    self.showDetails.toggle()
                }
                .sheet(isPresented: $showDetails) { Text(details ?? "") }
        }
    }
}
valexa
  • 4,462
  • 32
  • 48