如何从SwiftUI Canvas闭包中提取内部值?
SwiftUI Canvas闭包无法修改外部@State变量的解决方法
在SwiftUI中,尝试从Canvas的绘制闭包内修改外部@State变量时,会发现变量值始终无法更新,但闭包可以正常读取外部变量。以下是示例代码:
import SwiftUI struct ContentView3: View { @State var var1 = 0.25 @State var var2 = 0.75 @State var someStr = "Hey" var someVar = 0.5 var body: some View { Canvas { context, size in var1 = size.width // var1 始终不变化 var2 += 37 // var2 始终不变化 someStr = String("\(size.width)") // someStr 始终不变化 // 可以正常读取外部变量 someVar context.fill(Ellipse().path(in: CGRect(origin: .zero, size: CGSize(width: someVar*size.width, height: size.height))), with: .color(Color.blue)) } .frame(width: 400, height: 300) HStack { Spacer() Text("var1: \(var1)") Text(someStr) Text("var2: \(var2)") Spacer() } } } #Preview { ContentView3() }
问题原因
Canvas的绘制闭包是不可变执行环境,它的设计初衷仅用于完成绘制逻辑,而非修改视图状态:
- 闭包捕获的是外部变量的快照副本,直接修改无法同步到真正的
@State存储容器 - 闭包执行阶段处于视图绘制流程中,此时修改
@State不会触发视图更新机制,即便修改了值也无法在UI上体现
解决方案
方案1:用GeometryReader获取尺寸(简单场景首选)
通过GeometryReader直接获取视图的实际尺寸,在闭包外更新@State变量,再将尺寸传递给Canvas使用:
import SwiftUI struct ContentView3: View { @State var var1 = 0.25 @State var var2 = 0.75 @State var someStr = "Hey" let someVar = 0.5 var body: some View { GeometryReader { proxy in let canvasSize = proxy.size Canvas { context, size in context.fill(Ellipse().path(in: CGRect(origin: .zero, size: CGSize(width: someVar * size.width, height: size.height))), with: .color(.blue)) } // 视图首次出现时更新状态 .onAppear { updateState(with: canvasSize) } // 监听尺寸变化(适配视图动态调整的场景) .onChange(of: canvasSize) { newSize in updateState(with: newSize) } } .frame(width: 400, height: 300) HStack { Spacer() Text("var1: \(var1)") Text(someStr) Text("var2: \(var2)") Spacer() } } // 抽离状态更新逻辑,复用代码 private func updateState(with size: CGSize) { var1 = size.width var2 += 37 someStr = "\(size.width)" } } #Preview { ContentView3() }
方案2:用PreferenceKey传递尺寸(复杂层级场景)
如果Canvas是嵌套在多层子视图中,可通过自定义PreferenceKey传递尺寸,再在父视图中监听更新状态:
import SwiftUI // 定义偏好键,用于传递Canvas尺寸 struct CanvasSizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() } } struct ContentView3: View { @State var var1 = 0.25 @State var var2 = 0.75 @State var someStr = "Hey" let someVar = 0.5 var body: some View { Canvas { context, size in // 向父视图发送尺寸数据 context.preference(key: CanvasSizePreferenceKey.self, value: size) context.fill(Ellipse().path(in: CGRect(origin: .zero, size: CGSize(width: someVar * size.width, height: size.height))), with: .color(.blue)) } .frame(width: 400, height: 300) // 监听尺寸偏好变化,更新状态 .onPreferenceChange(CanvasSizePreferenceKey.self) { newSize in var1 = newSize.width var2 += 37 someStr = "\(newSize.width)" } HStack { Spacer() Text("var1: \(var1)") Text(someStr) Text("var2: \(var2)") Spacer() } } } #Preview { ContentView3() }
内容的提问来源于stack exchange,提问作者LGLB
相关产品推荐
相关产品推荐

