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

如何利用rect值实现SwiftUI路径动画?遇不可变属性错误求助

SwiftUI路径动画实现问题

我想用代码实现路径动画,当前ContentView代码如下:

struct ContentView: View {
    
    @State private var update: Bool = false
    
    var body: some View {
        
        VStack(spacing: 10.0) {
            
            VStack {
                
                MyShape(value: update)
                    .stroke(style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round))
                    .padding()
                    .animation(.default, value: update)
                
            }
            .frame(width: 300.0, height: 300.0)
            .background(Color.gray)
            .cornerRadius(10.0)
            
            
            Button("Update") {
                update.toggle()
            }
            
        }
        .padding(10.0)
        
    }
}

尝试用animatableData实现动画但未生效,想把Bool值转换为CGFloat或Double时出现如下错误:

Cannot assign to property: 'self' is immutable

对应的MyShape代码如下:

struct MyShape: Shape {
    
    var value: Bool
    private var dynamicValue: CGFloat = .zero
    
    var animatableData: CGFloat {
        get { return dynamicValue }
        set(newValue) { dynamicValue = newValue }
    }
    
    func path(in rect: CGRect) -> Path {
        
        if value {
            dynamicValue = rect.minY
        }
        else {
            dynamicValue = rect.maxY
        }
        
        return Path { path in
            
            path.move(to: CGPoint(x: rect.minX, y: rect.midY))
            path.addLine(to: CGPoint(x: rect.midX, y: dynamicValue))
            path.addLine(to: CGPoint(x: rect.maxX , y: rect.midY))
            
        }
        
    }
    
}

当前效果:
路径动画效果


问题解决

错误原因

在Shape的path(in:)方法中修改dynamicValue属性会报错,因为struct是值类型,path是实例方法,方法内的self是不可变的,无法修改结构体属性。

正确实现

我们需要将Bool值映射为0-1的CGFloat作为animatableData,动画过程中系统会自动插值这个值,再根据插值计算路径的坐标,实现平滑动画。修改后的MyShape代码如下:

struct MyShape: Shape {
    var value: Bool
    
    // 将Bool映射为0/1的CGFloat,作为动画插值的载体
    var animatableData: CGFloat {
        get { value ? 1 : 0 }
        set { value = newValue > 0.5 }
    }
    
    func path(in rect: CGRect) -> Path {
        // 根据animatableData的插值,计算当前的Y坐标
        let progress = animatableData
        // 从maxY向minY过渡:progress为0时取maxY,为1时取minY
        let currentY = rect.maxY - progress * (rect.maxY - rect.minY)
        
        return Path { path in
            path.move(to: CGPoint(x: rect.minX, y: rect.midY))
            path.addLine(to: CGPoint(x: rect.midX, y: currentY))
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
        }
    }
}

说明

  • animatableData负责在动画过程中传递0到1的插值,系统会自动处理动画的中间帧
  • 在path方法中,根据插值计算currentY,实现中间点在rect.minY和rect.maxY之间平滑移动
  • 无需额外修改ContentView,点击按钮后即可看到平滑的路径动画效果

内容的提问来源于stack exchange,提问作者swiftPunk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 04:17:17