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

SwiftUI拖动小圆至大圆边缘时colorInvert未动态更新颜色问题

问题

想要实现的效果:当位于大圆上方的小圆被拖动到大圆边缘时,小圆颜色反转,与背景形成对比。但拖动小圆至边缘时颜色并未反转,colorInvert() 未主动更新颜色值。

相关SwiftUI代码:

import SwiftUI

struct ContentView: View {
    
    @State private var location             = CGPoint.zero
    @State private var innerCircleLocation  = CGPoint.zero
    @GestureState private var fingerLocation: CGPoint? = nil
    private let bigCircleRadius: CGFloat    = 150

    var body: some View {
        
        GeometryReader { geometry in

            // some code here
            
            ZStack {

                // Larger circle
                Circle()
                    .frame(width: bigCircleRadius * 2, height: bigCircleRadius * 2)

                // Smaller circle
                Circle()     
                    .colorInvert()  // < ---
                    .frame(width: 75, height: 75)
                    .position(innerCircleLocation)
                    .gesture(fingerDrag)

            }
            // Initialize the location and innerCircleLocation
            .onAppear {
                location = CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2)
                innerCircleLocation = location
            }

            // some other code here

            }
        }
}
解决方案

问题核心有两点:

  1. 缺少拖拽手势的具体实现,fingerDrag未定义,导致小圆无法拖动到边缘位置
  2. colorInvert()是静态应用的,没有根据小圆位置动态切换是否启用

1. 实现拖拽手势,限制小圆移动范围

先定义拖拽手势,计算小圆实时位置并限制在大圆内部:

private var fingerDrag: some Gesture {
    DragGesture()
        .updating($fingerLocation) { value, state, _ in
            state = value.location
        }
        .onChanged { value in
            // 计算小圆相对大圆中心的偏移量
            let offset = CGPoint(
                x: value.location.x - location.x,
                y: value.location.y - location.y
            )
            let offsetLength = sqrt(offset.x * offset.x + offset.y * offset.y)
            
            // 限制小圆最大偏移量(大圆半径 - 小圆半径的一半)
            let maxOffset = bigCircleRadius - 75/2
            if offsetLength <= maxOffset {
                innerCircleLocation = value.location
            } else {
                // 到达边缘时,将小圆固定在大圆边界上
                let scale = maxOffset / offsetLength
                innerCircleLocation = CGPoint(
                    x: location.x + offset.x * scale,
                    y: location.y + offset.y * scale
                )
            }
        }
}

2. 动态切换颜色反转效果

添加计算属性判断小圆是否处于边缘,再根据状态决定是否应用colorInvert():

private var isAtEdge: Bool {
    let offset = CGPoint(
        x: innerCircleLocation.x - location.x,
        y: innerCircleLocation.y - location.y
    )
    let offsetLength = sqrt(offset.x * offset.x + offset.y * offset.y)
    let maxOffset = bigCircleRadius - 75/2
    // 用近似相等处理浮点误差
    return abs(offsetLength - maxOffset) < 1e-3
}

给View扩展一个条件式的colorInvert方法:

extension View {
    func colorInvert(_ condition: Bool) -> some View {
        condition ? self.colorInvert() : self
    }
}

最后修改小圆的颜色修饰:

Circle()     
    .colorInvert(isAtEdge)
    .frame(width: 75, height: 75)
    .position(innerCircleLocation)
    .gesture(fingerDrag)

完整修复代码

import SwiftUI

extension View {
    func colorInvert(_ condition: Bool) -> some View {
        condition ? self.colorInvert() : self
    }
}

struct ContentView: View {
    
    @State private var location             = CGPoint.zero
    @State private var innerCircleLocation  = CGPoint.zero
    @GestureState private var fingerLocation: CGPoint? = nil
    private let bigCircleRadius: CGFloat    = 150

    private var isAtEdge: Bool {
        let offset = CGPoint(
            x: innerCircleLocation.x - location.x,
            y: innerCircleLocation.y - location.y
        )
        let offsetLength = sqrt(offset.x * offset.x + offset.y * offset.y)
        let maxOffset = bigCircleRadius - 75/2
        return abs(offsetLength - maxOffset) < 1e-3
    }

    private var fingerDrag: some Gesture {
        DragGesture()
            .updating($fingerLocation) { value, state, _ in
                state = value.location
            }
            .onChanged { value in
                let offset = CGPoint(
                    x: value.location.x - location.x,
                    y: value.location.y - location.y
                )
                let offsetLength = sqrt(offset.x * offset.x + offset.y * offset.y)
                let maxOffset = bigCircleRadius - 75/2
                
                if offsetLength <= maxOffset {
                    innerCircleLocation = value.location
                } else {
                    let scale = maxOffset / offsetLength
                    innerCircleLocation = CGPoint(
                        x: location.x + offset.x * scale,
                        y: location.y + offset.y * scale
                    )
                }
            }
    }

    var body: some View {
        
        GeometryReader { geometry in
            ZStack {
                // 背景(可选,用于测试颜色反转)
                Color.white
                // Larger circle
                Circle()
                    .fill(Color.black)
                    .frame(width: bigCircleRadius * 2, height: bigCircleRadius * 2)

                // Smaller circle
                Circle()     
                    .fill(Color.black)
                    .colorInvert(isAtEdge)
                    .frame(width: 75, height: 75)
                    .position(innerCircleLocation)
                    .gesture(fingerDrag)

            }
            .onAppear {
                location = CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2)
                innerCircleLocation = location
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:23:13