如何在Swift中创建可感知触摸位置并返回x、y轴0-1取值的按钮
Swift 双轴坐标感知按钮实现方案
核心实现逻辑
- 捕获用户触摸在按钮视图范围内的坐标点,将坐标值与按钮自身的宽高做除法运算做归一化处理,得到0~1区间的映射值
- 触摸事件可通过UIKit的原生触摸响应回调、SwiftUI的手势Modifier两种方式实现
- 计算过程中需添加边界限制逻辑,保证最终输出值始终固定在0~1范围内,同时避免视图宽高为0时出现除零异常
注意:默认计算规则以视图左上角为坐标原点,若需要适配左下角为原点的通用坐标系,可将y轴的归一化结果替换为1 - 原始计算值
UIKit 实现方案
自定义UIControl子类,继承系统触摸事件回调实现坐标采集:
import UIKit class CoordinateAwareButton: UIControl { // 对外暴露的归一化坐标,x、y取值范围均为0~1 private(set) var normalizedPoint: CGPoint = .zero override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { super.beginTracking(touch, with: event) updateNormalizedPoint(touch: touch) sendActions(for: .touchDown) return true } override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { super.continueTracking(touch, with: event) updateNormalizedPoint(touch: touch) sendActions(for: .valueChanged) return true } override func endTracking(_ touch: UITouch?, with event: UIEvent?) { super.endTracking(touch, with: event) guard let touch = touch else { return } updateNormalizedPoint(touch: touch) sendActions(for: .touchUpInside) } private func updateNormalizedPoint(touch: UITouch) { let location = touch.location(in: self) let normalizedX = max(0, min(1, location.x / bounds.width)) let normalizedY = max(0, min(1, location.y / bounds.height)) normalizedPoint = CGPoint(x: normalizedX, y: normalizedY) } }
使用示例
// 初始化按钮 let trackButton = CoordinateAwareButton(frame: CGRect(x: 60, y: 100, width: 240, height: 240)) trackButton.backgroundColor = .systemBlue // 添加值变化监听 trackButton.addTarget(self, action: #selector(onButtonValueChanged), for: .valueChanged) view.addSubview(trackButton) @objc private func onButtonValueChanged(sender: CoordinateAwareButton) { print("当前归一化坐标 x:\(sender.normalizedPoint.x) y:\(sender.normalizedPoint.y)") }
SwiftUI 实现方案
通过DragGesture结合GeometryReader实现坐标采集:
import SwiftUI struct CoordinateAwareButton: View { @Binding var normalizedPoint: CGPoint let tapAction: () -> Void var body: some View { GeometryReader { geometry in RoundedRectangle(cornerRadius: 12) .fill(.blue) .gesture( DragGesture(minimumDistance: 0) .onChanged { value in let loc = value.location let normalizedX = max(0, min(1, loc.x / geometry.size.width)) let normalizedY = max(0, min(1, loc.y / geometry.size.height)) normalizedPoint = CGPoint(x: normalizedX, y: normalizedY) } .onEnded { _ in tapAction() } ) } } }
使用示例
struct DemoView: View { @State private var currentPoint = CGPoint.zero var body: some View { VStack(spacing: 24) { Text("当前坐标 x:\(String(format: "%.2f", currentPoint.x)) y:\(String(format: "%.2f", currentPoint.y))") .font(.headline) CoordinateAwareButton(normalizedPoint: $currentPoint) { print("按钮点击结束,最终坐标:\(currentPoint)") } .frame(width: 240, height: 240) } .padding() } }
可选优化点
- 如果需要支持触摸移出按钮范围后依然继续跟踪坐标,可在UIKit的
continueTracking方法中修改返回逻辑,或给SwiftUI手势指定全局坐标空间 - 可根据业务需求添加不同触摸阶段的回调事件,比如触摸开始、触摸取消等场景的自定义响应
内容的提问来源于stack exchange,提问作者Gabriel
相关产品推荐
相关产品推荐

