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

@Observable类成员无法触发UI更新问题排查与修复咨询

问题描述

使用@Observable宏实现状态管理,尝试将其作为环境对象及视图内Bindable变量使用:

  • 初始渲染正常,点击矩形时日志能打印状态变化(如下),但视图颜色未更新
  • 添加let _ = Self._printChanges()后,仅在视图出现时输出一次ContentView: @self, @identity, _fooWrapper changed.,点击无后续更新

日志示例:

from environment zoo false
from environment zoo true
from bindable false
from environment false
from bindable true
from environment true
from environment false
from environment true
from environment false
from bindable false
from bindable true
from bindable false
...

原代码

FooApp.swift

import SwiftUI

class Bar {
  var selected: Bool
  init() { self.selected = false }
}

protocol FooProtocol {
  var bar: Bar { get set }
}

@Observable
class FooWrapper {
  var foo: any FooProtocol
  init(foo: some FooProtocol) { self.foo = foo }
}

class Foo {
  var bar: Bar
  init() { self.bar = .init() }
}
extension Foo: FooProtocol {}

// Fully concrete, no existentials.
@Observable
class ZooWrapper {
  var foo: Foo
  init() { self.foo = Foo() }
}

@main
struct FooApp: App {
  @State private var foo = FooWrapper(foo: Foo())
  @State private var zoo = ZooWrapper()

  var body: some Scene {
    WindowGroup {
      ContentView().environment(foo)
    }
  }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
  @Environment(FooWrapper.self) private var fooWrapper
  
  var body: some View {
    @Bindable var fooWrapper1 = FooWrapper(foo: Foo())

    HStack {
      Rectangle()
        .fill(zooWrapper.foo.bar.selected ? Color.green : .blue)
        .frame(width: 50, height: 50)
        .onTapGesture {
          var bar = zooWrapper.foo.bar
          print("from environment zoo \(bar.selected)")
          bar.selected.toggle()
        }
      Rectangle()
        .fill(fooWrapper.foo.bar.selected ? Color.purple : .yellow)
        .frame(width: 50, height: 50)
        .onTapGesture {
          var bar = fooWrapper.foo.bar
          print("from environment \(bar.selected)")
          bar.selected.toggle()
        }
      Rectangle()
        .fill(fooWrapper1.foo.bar.selected ? Color.mint : .red)
        .frame(width: 50, height: 50)
        .onTapGesture {
          var bar = fooWrapper1.foo.bar
          print("from bindable \(bar.selected)")
          bar.selected.toggle()
        }
    }
  }
}

问题原因
  1. Bar类未标记@Observable:SwiftUI仅能感知被@Observable标记的类的属性变化,Bar作为存储核心状态的类未加该宏,导致状态修改无法触发视图刷新。
  2. 视图内@Bindable变量生命周期错误:body内定义的fooWrapper1会在每次视图刷新时重新创建,修改的是临时实例的状态,无法持久化也无法触发视图更新。
  3. ZooWrapper未传递到视图:原ContentView中直接使用zooWrapper但未定义该变量,属于代码逻辑漏洞。
  4. 冗余的状态拷贝:onTapGesture中拷贝Bar实例的引用(var bar = ...)属于冗余写法,虽不影响状态修改,但增加代码复杂度。

修复方案

1. 为Bar添加@Observable宏

让SwiftUI能追踪selected属性的变化:

@Observable
class Bar {
  var selected: Bool
  init() { self.selected = false }
}

2. 修正ContentView的变量生命周期

将fooWrapper1移到视图属性中,避免每次刷新重建:

struct ContentView: View {
  @Environment(FooWrapper.self) private var fooWrapper
  @Environment(ZooWrapper.self) private var zooWrapper // 注入ZooWrapper环境对象
  @State private var fooWrapper1 = FooWrapper(foo: Foo()) // 移到属性层

  var body: some View {
    @Bindable var bindableFooWrapper1 = fooWrapper1 // 用@Bindable包装State变量

    HStack {
      // ... 其他视图代码
    }
  }
}

3. 传递ZooWrapper到视图

在FooApp中添加环境注入:

@main
struct FooApp: App {
  @State private var foo = FooWrapper(foo: Foo())
  @State private var zoo = ZooWrapper()

  var body: some Scene {
    WindowGroup {
      ContentView()
        .environment(foo)
        .environment(zoo) // 注入ZooWrapper
    }
  }
}

4. 简化状态修改逻辑

直接修改原对象属性,去掉冗余的引用拷贝:

// 第一个矩形的点击事件
.onTapGesture {
  zooWrapper.foo.bar.selected.toggle()
  print("from environment zoo \(zooWrapper.foo.bar.selected)")
}

// 第二个矩形的点击事件
.onTapGesture {
  fooWrapper.foo.bar.selected.toggle()
  print("from environment \(fooWrapper.foo.bar.selected)")
}

// 第三个矩形的点击事件
.onTapGesture {
  bindableFooWrapper1.foo.bar.selected.toggle()
  print("from bindable \(bindableFooWrapper1.foo.bar.selected)")
}

内容的提问来源于stack exchange,提问作者g i i k

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 13:14:50