You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:12:06