[英]iOS - SwiftUI - @State variable value in viewBuilder has old value
I am using a LoadingView
view to show an activity indicator before data is loaded.我正在使用LoadingView
视图在加载数据之前显示活动指示器。 The LoadingView
receives a @ViewBuilder
closure to render the content after the data is loaded. LoadingView
接收一个@ViewBuilder
闭包以在数据加载后呈现内容。
The @ViewBuilder
closure captures a @State
variable, but if this state was updated, the @ViewBuilder
gets the old value for the state. @ViewBuilder
闭包捕获一个@State
变量,但如果此 state 已更新, @ViewBuilder
将获取 state 的旧值。
An easy code snippet is worth thousand words:一个简单的代码片段值一千字:
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
}
}
}
}
In this example, the presentation mode is changed to .big
before the data is loaded.在此示例中,在加载数据之前将演示模式更改为.big
。 When the @ViewBuilder
is rendered, it still uses the initial .small
value.当@ViewBuilder
被渲染时,它仍然使用初始的.small
值。
Some findings: If presentation mode is changed to .big
after the data is loaded, the @ViewBuilder
view gets updated properly.一些发现:如果在加载数据后将显示模式更改为.big
, @ViewBuilder
视图会正确更新。 It is my understanding, that internally the @ViewBuilder
view does only bind to the state after it has been presented.据我了解, @ViewBuilder
视图在内部仅在呈现后才绑定到 state。 Still, it should be possible for the @ViewBuilder
to get the current value of the state.不过,@ViewBuilder 应该可以获取@ViewBuilder
的当前值。 After all, when the @ViewBuilder
is presented, the binding to the state works correctly.毕竟,当@ViewBuilder
出现时,与 state 的绑定工作正常。
Using an @ObservedObject
to wrap the presentationMode
works fine, but I would like to avoid this, in order to use @State
reliable.使用@ObservedObject
来包装presentationMode
工作正常,但我想避免这种情况,以便使用@State
可靠。
Thanks for the help!谢谢您的帮助!
You are not using the content
in LoadingView
until the data is loaded, so SwiftUI fails to recreate the LoadingView
when presentationMode
is updated.在加载数据之前,您不会使用LoadingView
中的content
,因此 SwiftUI 在更新presentationMode
时无法重新创建LoadingView
。 There are a number of ways to fix this.有很多方法可以解决这个问题。
content
as capturing presentationMode
将content
标记为捕获presentationMode
If you mark the content
of LoadingView
as capturing presentationMode
, then LoadingView
will get updated when presentationMode
changes.如果您将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
from the start从一开始就使用content
Because your LoadingView
isn't using the content
when presentationMode
is changed, SwiftUI fails to recreate the LoadingView
so it is stuck with the original value of presentationMode
.因为您的LoadingView
在presentationMode
更改时没有使用content
,所以 SwiftUI 无法重新创建LoadingView
,因此它卡在了presentationMode
的原始值上。
If you change your LoadingView
to use the content
but make it invisible until loaded, then the LoadingView
will get updated when presentationMode
changes:如果您将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
to control loaded
State启用LoadingView
以控制loaded
的 State If you only want the real content
to be rendered when the data is loaded, you can wrap that content in if loaded { }
and pass loaded
as a Binding
to the LoadingView
, and then LoadingView
can set loaded = true
when the data is loaded, then the PresenterView
will reload LoadingView
with the real content
which will use the latest versions of the @State
variables:如果您只想在加载数据时呈现真实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.