【问题标题】:Updating a @State property from within a SwiftUI View从 SwiftUI 视图中更新 @State 属性
【发布时间】:2021-10-19 05:48:55
【问题描述】:

我有一个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()! }
}

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

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($0.name)
                }
            }
            .pickerStyle(.segmented)
        }
    }
}

现在我遇到的问题是,此数据包含应选择哪个段。在我的真实应用中,这是一个获取数据的 Web 请求,其中包含已选择的段。

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

如果我只是添加一行

self.selectedInterface = result.selectedInterface

进入我的AsyncContentView 我收到此错误

类型“()”不能符合“视图”

【问题讨论】:

  • 您不能将这种类型的代码放在视图中。您可以尝试将其添加到 Picker:.onAppear { self.selectedInterface = result.selectedInterface }
  • 谢谢。我看到这确实更新了selectedInterface,但它似乎没有选择一个段。
  • @Darren 另一种方法是使用let _ = ...。例如要在视图正文中打印,请执行let _ = print("test")。此方法与onAppear 不同,因为每次重新计算body 时都会发生这种情况,而不仅仅是第一次出现。
  • John Sundell:“可以肯定的是,上述模式对于更简单的视图非常有效——然而,将视图代码与数据加载和网络等任务混合并不是一个好的实践,因为随着时间的推移,这样做往往会导致非常混乱和相互交织的实现。” 所以,感谢您为我们提供了这种反模式的完美示例;)

标签: swift async-await swiftui swift5.5 swift-concurrency


【解决方案1】:

您可以在生成内容的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($0.name).tag(Optional($0))       // << here !!
            }
        }
        .pickerStyle(.segmented)
        .onAppear {
            selected.wrappedValue = result.selectedInterface  // << here !!
        }
    }
}

【讨论】:

  • 虽然我可以看到wrappedValue 的更改使用了print(selected.wrappedValue),但它似乎并没有选择一个段。
  • 尝试在 Picker 中也使用它
  • 不,仍然没有运气。我确认@State 正在更改使用.onChange(of: selectedInterface) 记录,但仍然没有选择段。就像@State 更改时它没有重绘视图一样。
  • 我明白了 - Picker 中的选择和项目应该是相同的类型 - 使用标签或将选择类型更改为字符串
  • 谢谢,我现在可以使用了,但前提是我将选择设置为 String.tag($0.name)。不知道为什么它不能使用 Interface 本身作为标签。
猜你喜欢
  • 1970-01-01
  • 2023-01-26
  • 2021-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-31
  • 2021-08-06
相关资源
最近更新 更多