在视图中,我调用一个引用一些外部对象并更新图表的函数。在函数处理时,我想隐藏图表并将其替换为
ProgressView
。问题:如何从函数内部控制@State的值。
我尝试了不同的版本,下面的代码在线返回错误:
numbers = updateNumbers()
不能对不可变值使用变异成员:
是不可变的'self'
Swift 游乐场。
import SwiftUI
import Charts
import PlaygroundSupport
struct ProgressExample: View {
@State private var isUpdating: Bool = false
@State private var numbers: [Int] = []
var body: some View {
VStack {
Button("Update data") {
isUpdating = true // Triggered refresh, show ProgressView
numbers = updateNumbers()
}
.padding()
Spacer()
if (isUpdating) {
ProgressView("Updating")
} else {
List {
ForEach(numbers, id: \.self) { number in
Text("Number: \(number)")
}
}
}
}
}
// Long running function
mutating func updateNumbers() -> [Int] {
var randomIntegers: [Int] = []
for _ in 0...10 {
randomIntegers.append(Int.random(in: 1...100))
Thread.sleep(forTimeInterval: 0.1)
}
self.isUpdating = false // The method completed, hide ProgressView
return randomIntegers
}
}
// Present the view in the Live View window
PlaygroundPage.current.setLiveView(ProgressExample())
该函数不需要是
mutating
。 State.wrappedValue
有一个 nonmutating setter,因为它实际上会改变 State
中的某些引用类型值,而不是改变 struct
值。
所以只需删除
mutating
func updateNumbers() -> [Int] {
var randomIntegers: [Int] = []
for _ in 0...10 {
randomIntegers.append(Int.random(in: 1...100))
Thread.sleep(forTimeInterval: 0.1)
}
self.isUpdating = false
return randomIntegers
}
在将视图设置为游乐场实时视图时,我还会给该视图一个框架,因为游乐场显然无法找到合适的尺寸来显示其实时视图。
PlaygroundPage.current.setLiveView(
ProgressExample()
.frame(width: 700, height: 700)
)
虽然它在游乐场中不起作用,但我建议使用
#Preview
来玩你的 SwiftUI 视图。
#Preview {
ProgressExample()
}
现在请注意,进度视图实际上并未显示,因为
Thread.sleep
会阻塞 UI 线程并停止 UI 更新。我希望在你的真实代码中你没有在这里调用阻塞函数。
无论如何,我强烈建议将
updateNumbers
制作为 async
方法并在 .task
修饰符中调用它。对于这里的简单示例,您可以像这样重写视图:
struct ProgressExample: View {
@State private var isUpdating: Bool = false
@State private var numbers: [Int] = []
var body: some View {
VStack {
Button("Update data") {
isUpdating = true
}
.padding()
Spacer()
if isUpdating {
ProgressView("Updating")
} else {
List {
ForEach(numbers, id: \.self) { number in
Text("Number: \(number)")
}
}
}
}
.task(id: isUpdating) {
if isUpdating {
numbers = await updateNumbers()
}
}
}
func updateNumbers() async -> [Int] {
var randomIntegers: [Int] = []
for _ in 0...10 {
randomIntegers.append(Int.random(in: 1...10000))
do {
try await Task.sleep(for: .milliseconds(100))
} catch {
break // task has been cancelled
}
}
self.isUpdating = false
return randomIntegers
}
}