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 } } }
解决方案
问题核心有两点:
- 缺少拖拽手势的具体实现,
fingerDrag未定义,导致小圆无法拖动到边缘位置 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
相关产品推荐
相关产品推荐

