从 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 }
}
以及 LoadingState
和 Loader
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 !!
}
}
}
我有一个 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 }
}
以及 LoadingState
和 Loader
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 !!
}
}
}