从 SwiftUI 视图中更新 @State 属性

Updating a @State property from within a SwiftUI View

我有一个 AsyncContentView 处理视图出现时的数据加载并处理加载视图和内容的切换(取自此处 swiftbysundell):

struct AsyncContentView<P:Parsable, Source:Loader<P>, Content: View>: View {
    
    @ObservedObject private var source: Source
    private var content: (P.ReturnType) -> Content
    
    init?(source: Source, reloadAfter reloadTime:UInt64 = 0, @ViewBuilder content: @escaping (P.ReturnType) -> Content) {
        self.source = source
        self.content = content
    }
    
    func loadInfo() {
        Task {
            await source.loadData()
        }
    }
    
    var body: some View {
        switch source.state {
        case .idle:
            return AnyView(Color.clear.onAppear(perform: loadInfo))
        case .loading:
            return AnyView(ProgressView("Loading..."))
        case .loaded(let output):
            return AnyView(content(output))
        }
    }
}

为了完整起见,这里是 Parsable 协议:

protocol Parsable: ObservableObject {
    associatedtype ReturnType
    init()
    var result: ReturnType { get }
}

以及 LoadingStateLoader

enum LoadingState<Value> {
    case idle
    case loading
    case loaded(Value)
}

@MainActor
class Loader<P:Parsable>: ObservableObject {
    @Published public var state: LoadingState<P.ReturnType> = .idle
    func loadData() async {
        self.state = .loading
        await Task.sleep(2_000_000_000)
        self.state = .loaded(P().result)
    }
}

这是我使用的一些虚拟数据:

struct Interface: Hashable {
    let name:String
}
struct Interfaces {
    let interfaces: [Interface] = [
        Interface(name: "test1"),
        Interface(name: "test2"),
        Interface(name: "test3")
    ]
    var selectedInterface: Interface { interfaces.randomElement()! }
}

现在我像这样把它们放在一起,完成了它的工作。它处理显示加载视图 2 秒的 async 函数,然后使用提供的数据生成内容视图:

struct ContentView: View {
    
    class SomeParsableData: Parsable {
        typealias ReturnType = Interfaces
        required init() { }
        var result = Interfaces()
    }
    
    @StateObject var pageLoader: Loader<SomeParsableData> = Loader()
    @State private var selectedInterface: Interface?
    
    var body: some View {
        AsyncContentView(source: pageLoader) { result in
            Picker(selection: $selectedInterface, label: Text("Selected radio")) {
                ForEach(result.interfaces, id: \.self) {
                    Text([=15=].name)
                }
            }
            .pickerStyle(.segmented)
        }
    }
}

现在我遇到的问题是这个数据包含应该选择哪个段。在我的真实应用程序中,这是一个获取数据的网络请求,其中包括选择了哪个段。

那么我怎样才能让这个视图更新 selectedInterface @state 属性?

如果我简单地添加行

self.selectedInterface = result.selectedInterface

进入我的 AsyncContentView 我得到这个错误

Type '()' cannot conform to 'View'

你可以在生成内容的 onAppear 中做到这一点,但我想最好不要直接这样做,而是通过绑定(就像对状态的外部存储的引用)一样,比如

var body: some View {
    let selected = self.$selectedInterface
    AsyncContentView(source: pageLoader) { result in
        Picker(selection: selected, label: Text("Selected radio")) {
            ForEach(result.interfaces, id: \.self) {
                Text([=10=].name).tag(Optional([=10=]))       // << here !!
            }
        }
        .pickerStyle(.segmented)
        .onAppear {
            selected.wrappedValue = result.selectedInterface  // << here !!
        }
    }
}