SwiftUI中@resultBuilder自定义Shape在.onAppear()不更新的问题
问题分析与解决方案
问题根源
你的CustomShape中,@SumBuilder let sum: () -> Int是一个闭包,它在ContentView初始化时捕获了distance的初始值。当onAppear修改distance时,虽然@State的值更新了,但CustomShape的sum闭包没有被重新生成,SwiftUI也未检测到闭包的依赖变化,导致Shape没有重新计算path;而点击按钮时,动作触发了视图重建,sum闭包被重新创建并捕获新值,因此Shape能正常更新。
解决方案1:将依赖变量作为CustomShape的直接属性(推荐)
修改CustomShape,把需要依赖的变量作为属性传入,再通过@SumBuilder构建求和逻辑,让SwiftUI能通过属性变化自动触发Shape重绘:
@resultBuilder struct SumBuilder { static func buildBlock(_ components: Int...) -> Int { components.reduce(0, +) } } struct CustomShape: Shape { let distance: Int // 用@SumBuilder构建求和逻辑,直接使用传入的distance @SumBuilder var sum: () -> Int { distance } func path(in rect: CGRect) -> Path { Path { path in path.move(to: .zero) path.addLine(to: CGPoint(x: sum(), y: sum())) } } } struct ContentView: View { @State private var distance = 0 var body: some View { VStack { CustomShape(distance: distance) .stroke(Color.red, lineWidth: 2) .frame(width: 200, height: 200) Button("Set to 200") { distance = 200 } } .onAppear { distance = 100 } } }
这种方式完全符合SwiftUI的视图更新机制,无论是onAppear还是按钮修改distance,都能触发Shape的path重新计算,也能适配你实际项目中使用GeometryProxy的场景——只需将GeometryProxy获取的中心坐标作为属性传入CustomShape即可。
解决方案2:通过id强制重建视图
如果必须保留闭包形式的sum,可以通过添加.id()修饰符,让distance变化时强制重建CustomShape,生成新的闭包捕获最新值:
struct CustomShape: Shape { let sum: () -> Int init(@SumBuilder sum: @escaping () -> Int) { self.sum = sum } func path(in rect: CGRect) -> Path { Path { path in path.move(to: .zero) path.addLine(to: CGPoint(x: sum(), y: sum())) } } } struct ContentView: View { @State private var distance = 0 var body: some View { VStack { CustomShape { distance } .stroke(Color.red, lineWidth: 2) .frame(width: 200, height: 200) // 当distance变化时,强制重建视图 .id(distance) Button("Set to 200") { distance = 200 } } .onAppear { distance = 100 } } }
解决方案3:使用@Observable(iOS 17+)
如果项目基于iOS 17及以上,可以将依赖变量包装成可观察对象,让CustomShape感知变量变化:
@Observable class DistanceModel { var distance = 0 } struct CustomShape: Shape { let model: DistanceModel @SumBuilder var sum: () -> Int { model.distance } func path(in rect: CGRect) -> Path { Path { path in path.move(to: .zero) path.addLine(to: CGPoint(x: sum(), y: sum())) } } } struct ContentView: View { @State private var model = DistanceModel() var body: some View { VStack { CustomShape(model: model) .stroke(Color.red, lineWidth: 2) .frame(width: 200, height: 200) Button("Set to 200") { model.distance = 200 } } .onAppear { model.distance = 100 } } }
内容的提问来源于stack exchange,提问作者rayaantaneja
相关产品推荐
相关产品推荐

