[英]iOS 15 SwiftUI 3 Picker binding is not working after changing @State value
[英]iOS - SwiftUI - @State variable value in viewBuilder has old value
我正在使用LoadingView
视图在加载数据之前显示活动指示器。 LoadingView
接收一个@ViewBuilder
闭包以在数据加载后呈现内容。
@ViewBuilder
闭包捕获一个@State
变量,但如果此 state 已更新, @ViewBuilder
将获取 state 的旧值。
一个简单的代码片段值一千字:
struct PresenterView: View {
enum PresentationMode {
case small, big
}
@State private var presentationMode: PresentationMode = .small
var body: some View {
LoadingView() { message in
switch self.presentationMode {
case .small:
Text("Small presentation of: \(message)")
case .big:
Text("Big presentation of: \(message)")
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("Presentation mode is changed, before data is loaded")
self.presentationMode = .big
}
}
}
}
struct LoadingView<Content: View>: View {
@State var loaded = false
let content: (String) -> Content
init(@ViewBuilder content: @escaping (String) -> Content) {
self.content = content
}
var body: some View {
Group {
if loaded {
content("Data")
}
else {
Text("Data is still loading")
}
}
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("Data is loaded")
self.loaded = true
}
}
}
}
在此示例中,在加载数据之前将演示模式更改为.big
。 当@ViewBuilder
被渲染时,它仍然使用初始的.small
值。
一些发现:如果在加载数据后将显示模式更改为.big
, @ViewBuilder
视图会正确更新。 据我了解, @ViewBuilder
视图在内部仅在呈现后才绑定到 state。 不过,@ViewBuilder 应该可以获取@ViewBuilder
的当前值。 毕竟,当@ViewBuilder
出现时,与 state 的绑定工作正常。
使用@ObservedObject
来包装presentationMode
工作正常,但我想避免这种情况,以便使用@State
可靠。
谢谢您的帮助!
在加载数据之前,您不会使用LoadingView
中的content
,因此 SwiftUI 在更新presentationMode
时无法重新创建LoadingView
。 有很多方法可以解决这个问题。
content
标记为捕获presentationMode
如果您将LoadingView
的content
标记为捕获presentationMode
,则LoadingView
将在presentationMode
更改时更新。
struct ContentView: View {
enum PresentationMode {
case small, big
}
@State private var presentationMode: PresentationMode = .small
var body: some View {
LoadingView() { [presentationMode] message in
switch presentationMode {
case .small:
Text("Small presentation of: \(message)")
case .big:
Text("Big presentation of: \(message)")
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("Presentation mode is changed, before data is loaded")
self.presentationMode = .big
}
}
}
}
content
因为您的LoadingView
在presentationMode
更改时没有使用content
,所以 SwiftUI 无法重新创建LoadingView
,因此它卡在了presentationMode
的原始值上。
如果您将LoadingView
更改为使用content
但使其在加载之前不可见,则LoadingView
将在presentationMode
更改时更新:
var body: some View {
ZStack {
content("Data")
.opacity(loaded ? 1 : 0)
if !loaded {
Text("Data is still loading")
}
}
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.loaded = true
}
}
}
LoadingView
以控制loaded
的 State 如果您只想在加载数据时呈现真实content
,您可以将该内容包装在if loaded { }
中并将loaded
作为Binding
传递给LoadingView
,然后LoadingView
可以在加载数据时设置loaded = true
,然后PresenterView
将使用最新版本的@State
变量重新加载LoadingView
的真实content
:
struct PresenterView: View {
enum PresentationMode {
case small, big
}
@State private var presentationMode: PresentationMode = .small
@State private var loaded = false
var body: some View {
LoadingView(loaded: $loaded) { message in
if loaded {
let _ = print("presenting now")
switch self.presentationMode {
case .small:
Text("Small presentation of: \(message)")
case .big:
Text("Big presentation of: \(message)")
}
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("Presentation mode is changed, before data is loaded")
self.presentationMode = .big
}
}
}
}
struct LoadingView<Content: View>: View {
@Binding var loaded: Bool
let content: (String) -> Content
init(loaded: Binding<Bool>, @ViewBuilder content: @escaping (String) -> Content) {
self._loaded = loaded
self.content = content
}
var body: some View {
ZStack {
content("Data")
if !loaded {
Text("Data is still loading")
}
}
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.loaded = true
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.