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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:35:21