sheet(unwrapping:) shows a blank view
Closed this issue · 1 comments
Description
When using .sheet(unwrapping:)
to present a sheet when an optional value becomes non-nil, the sheet is always blank.
Given the following code from State-driven navigation:
struct FeatureView: View {
@State var presentedValue: String?
var body: some View {
Button("Show sheet") {
self.presentedValue = "Hello!"
}
.sheet(unwrapping: self.$presentedValue) { $value in
TextField("Value", text: $value)
}
}
}
When I tap the "Show sheet" button, I'd expect to see a sheet with the TextField containing "Hello!". Instead, I see a blank sheet.
This seems to be broken on main
as well as version 1.3.0
, but it worked in version 0.6.1
. I'm not sure which version introduced this issue.
Checklist
- I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
- If possible, I've reproduced the issue using the
main
branch of this package. - This issue hasn't been addressed in an existing GitHub issue or discussion.
Expected behavior
I expected to see the TextField in a sheet with the text "Hello!".
Actual behavior
I saw a blank sheet.
Steps to reproduce
With the code above, this is what I see when I tap the button:
Simulator.Screen.Recording.-.iPhone.15.-.2024-05-10.at.14.21.46.mp4
Full project attached (though it's just the above code inside an otherwise empty project).
SwiftUI Navigation version information
1.3.0
Destination operating system
iOS 17.4
Xcode version information
15.3 (15E204a)
Swift Compiler version information
swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0
Unfortunately this seems to be a SwiftUI bug with @State
and derived bindings. If you change to use an observable object it works fine:
class FeatureModel: ObservableObject {
@Published var presentedValue: String?
}
struct FeatureView: View {
@ObservedObject var model = FeatureModel()
var body: some View {
Button("Show sheet") {
self.model.presentedValue = "Hello!"
}
.sheet(unwrapping: self.$model.presentedValue) { $value in
TextField("Value", text: $value)
}
}
}
It also works fine with the newer @Observable
macro and @Bindable
:
@Observable
class FeatureModel {
var presentedValue: String?
}
struct FeatureView: View {
@Bindable var model = FeatureModel()
var body: some View {
Button("Show sheet") {
self.model.presentedValue = "Hello!"
}
.sheet(unwrapping: self.$model.presentedValue) { $value in
TextField("Value", text: $value)
}
}
}
And it even works with @State
if you create an ad hoc binding:
struct FeatureView: View {
@State var presentedValue: String?
var body: some View {
Button("Show sheet") {
self.presentedValue = "Hello!"
}
.sheet(
unwrapping: Binding(
get: { presentedValue },
set: { presentedValue = $0 }
)
) { $value in
Text("Hi")
TextField("Value", text: $value)
}
}
}
While we'd love to work around the bug in the library, I'm not sure if it's possible to detect if a binding is being derived from @State
vs. something else. If anyone has an idea, though, we'd definitely be interested!
Note that ad hoc bindings can be buggy when it comes to SwiftUI animations and transactions, which is why this library prefers derived bindings. Sadly it seems that you should generally prefer to avoid @State
in SwiftUI if you want to avoid @State
-specific bugs.
Since this is a bug with SwiftUI and not the library, I'm going to convert to a discussion, but good find!