SwiftUI中通过Model引用的视图无法更新问题求助
问题分析:SwiftUI点击按钮后矩形未更新的原因
运行下述代码时,点击「move cursor」按钮后矩形并未更新,但不通过Model直接操作视图时功能正常,预期cursorRect这个@State字段的变化能触发ZStack中Rectangle的更新。
class TestZStackModel { var view: TestZStackView! = nil func setRect() { view.cursorRect = CGRect(x: CGFloat.random(in: 0 ... 100), y: CGFloat.random(in: 0 ... 100), width: 100, height: 100) } } struct TestZStackView: View { @State var cursorRect: CGRect = .zero let model: TestZStackModel var body: some View { VStack { ZStack { Canvas { graphics, size in print("redrawing canvas") graphics.stroke( Path(ellipseIn: CGRect(origin: .zero, size: size)), with: .color(.green), lineWidth: 4) } Rectangle() .position(x: cursorRect.midX, y: cursorRect.midY) .frame(width: cursorRect.width, height: cursorRect.height) } .frame(maxHeight: .infinity) Button(action: { model.setRect() }, label: { Text("move cursor") }) } } } func makeZStackView() -> some View { let model = TestZStackModel() let view = TestZStackView(model: model) model.view = view return view } struct ContentView: View { var body: some View { makeZStackView() .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() } }
核心原因
- 违反
@State使用规则:@State是SwiftUI专为视图内部状态设计的属性包装器,它的读写必须局限在视图的body或内部闭包中。你的Model直接持有View实例并修改其@State属性,这种外部操作无法触发SwiftUI的视图刷新机制——@State的状态变更通知和视图绑定,外部直接赋值不会触发通知。 - 值类型视图实例不一致:SwiftUI的View是值类型,视图刷新时SwiftUI可能会创建新的View实例。你赋值给Model的是最初创建的View实例,后续界面显示的是新实例,修改旧实例的属性自然不会影响当前界面。
解决方案
方案1:采用MVVM模式(推荐)
将Model改为可观察对象,用@Published标记状态,视图通过@ObservedObject订阅状态变化:
import SwiftUI class TestZStackModel: ObservableObject { @Published var cursorRect: CGRect = .zero func setRect() { cursorRect = CGRect(x: CGFloat.random(in: 0 ... 100), y: CGFloat.random(in: 0 ... 100), width: 100, height: 100) } } struct TestZStackView: View { @ObservedObject var model: TestZStackModel var body: some View { VStack { ZStack { Canvas { graphics, size in print("redrawing canvas") graphics.stroke( Path(ellipseIn: CGRect(origin: .zero, size: size)), with: .color(.green), lineWidth: 4) } Rectangle() .position(x: model.cursorRect.midX, y: model.cursorRect.midY) .frame(width: model.cursorRect.width, height: model.cursorRect.height) } .frame(maxHeight: .infinity) Button(action: { model.setRect() }, label: { Text("move cursor") }) } } } func makeZStackView() -> some View { let model = TestZStackModel() return TestZStackView(model: model) } struct ContentView: View { var body: some View { makeZStackView() .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() } }
方案2:视图内部管理状态(简单场景)
如果不需要独立的Model,直接把状态和逻辑放在视图内部:
struct TestZStackView: View { @State private var cursorRect: CGRect = .zero var body: some View { VStack { ZStack { Canvas { graphics, size in print("redrawing canvas") graphics.stroke( Path(ellipseIn: CGRect(origin: .zero, size: size)), with: .color(.green), lineWidth: 4) } Rectangle() .position(x: cursorRect.midX, y: cursorRect.midY) .frame(width: cursorRect.width, height: cursorRect.height) } .frame(maxHeight: .infinity) Button(action: { cursorRect = CGRect(x: CGFloat.random(in: 0 ... 100), y: CGFloat.random(in: 0 ... 100), width: 100, height: 100) }, label: { Text("move cursor") }) } } }
内容的提问来源于stack exchange,提问作者Samuel Cederlund
相关产品推荐
相关产品推荐

