从函数内部控制@State值

问题描述 投票:0回答:1

在视图中,我调用一个引用一些外部对象并更新图表的函数。在函数处理时,我想隐藏图表并将其替换为

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())
swift swiftui uiprogressview swiftui-view mutating-function
1个回答
0
投票

该函数不需要是

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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.