SwiftUI中如何同时实现旋转、平移与缩放手势?
解决SwiftUI中签名视图的平移、缩放与旋转手势冲突问题
修复后的完整代码
struct SignatureView: View { @State private var location: CGPoint = CGPoint(x: 150, y: 300) @State private var width: CGFloat = 100 @State private var height: CGFloat = 100 @State private var totalRotation: Double = 0.0 @GestureState private var tempRotation: Double = 0.0 private var currentRotation: Double { totalRotation + tempRotation } var body: some View { VStack { GeometryReader { geometry in ZStack { // 可交互的签名容器 ZStack(alignment: .bottomTrailing) { Rectangle() .stroke(style: StrokeStyle(lineWidth: 1, dash: [5])) .fill(.blue) .frame(width: width, height: height) // 黑色控制旋钮(同时处理缩放和旋转) Circle() .fill(.black) .frame(width: 25, height: 25) .gesture(knobGesture) .zIndex(1) } .rotationEffect(Angle(degrees: currentRotation)) .position(location) .gesture(panGesture) } .frame(width: geometry.size.width, height: geometry.size.height) } } .background(Color.black.opacity(0.3)) .overlay( VStack { Spacer() Button("Save") {} } ) .ignoresSafeArea(.all) } // 旋钮的缩放+旋转手势 private var knobGesture: some Gesture { DragGesture(minimumDistance: 0) .updating($tempRotation) { value, state, transaction in // 将拖拽点转换到未旋转的容器本地坐标系,抵消当前旋转的影响 let containerCenter = CGPoint(x: width/2, y: height/2) let rotationTransform = CGAffineTransform(rotationAngle: -CGFloat(currentRotation * .pi / 180)) let translatedTransform = rotationTransform.translatedBy(x: -containerCenter.x, y: -containerCenter.y) let startPoint = value.startLocation.applying(translatedTransform) let currentPoint = value.location.applying(translatedTransform) // 计算旋转角度变化 let startAngle = atan2(startPoint.y, startPoint.x) let currentAngle = atan2(currentPoint.y, currentPoint.x) state = Double((currentAngle - startAngle) * 180 / .pi) // 计算缩放比例,保证尺寸等比例变化 let startDistance = hypot(startPoint.x, startPoint.y) let currentDistance = hypot(currentPoint.x, currentPoint.y) let scale = currentDistance / startDistance width = max(50, width * scale) height = width } .onEnded { _ in totalRotation += tempRotation } } // 容器的平移手势 private var panGesture: some Gesture { DragGesture() .onChanged { value in location.x += value.translation.width location.y += value.translation.height } } func loadImageFromDocumentDirectory(filename: String) -> UIImage? { let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let fileURL = documentsDirectory.appendingPathComponent(filename) do { let imageData = try Data(contentsOf: fileURL) return UIImage(data: imageData) } catch { print("Error loading image: \(error)") return nil } } }
关键修改说明
- 合并旋钮手势逻辑:把缩放和旋转整合到同一个
DragGesture中,彻底解决手势竞争问题。拖拽黑色旋钮时,可同时通过拖拽的径向距离调整尺寸,通过角度变化调整旋转角度。 - 修正坐标系计算:计算旋转角度前,将拖拽点转换到未旋转的容器本地坐标系,抵消当前旋转状态对角度计算的干扰,确保旋转逻辑在任何角度下都准确。
- 简化状态管理:用
GestureState临时存储旋转增量,避免重复计算;移除冗余的fingerLocation、startLocation等状态,让代码更简洁。 - 优化手势优先级:平移手势绑定到容器视图,旋钮手势绑定到黑色圆形,SwiftUI会自动处理手势传递,保证容器平移和旋钮操作互不干扰。
- 统一缩放逻辑:通过拖拽点到容器中心的距离比例计算缩放,比原代码的
translation.width更直观准确,确保尺寸始终等比例变化。
内容的提问来源于stack exchange,提问作者Rin Star
相关产品推荐
相关产品推荐

