繁体   English   中英

iOS - SwiftUI - viewBuilder 中的@State 变量值具有旧值

[英]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

如果您将LoadingViewcontent标记为捕获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

因为您的LoadingViewpresentationMode更改时没有使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM