99 lines
3.4 KiB
Swift
99 lines
3.4 KiB
Swift
import UIKit
|
||
import SwiftUI
|
||
|
||
/// UIKit 触控捕获视图:将触摸转换为相对坐标(0.0~1.0)的远端输入指令。
|
||
/// 与 Android 端 RemoteTouchView 行为一致:
|
||
/// - 实时转发原始 MotionEvent(DOWN=0 / UP=1 / MOVE=2 / CANCEL=3),远端注入实现"跟手"
|
||
/// - 手指抬起时若判定为滑动(时长 >= 200ms 或位移 >= 2%),额外发送一条 SWIPE 指令
|
||
final class RemoteTouchView: UIView {
|
||
|
||
/// Android MotionEvent 动作常量
|
||
enum MotionAction {
|
||
static let down: Int32 = 0
|
||
static let up: Int32 = 1
|
||
static let move: Int32 = 2
|
||
static let cancel: Int32 = 3
|
||
}
|
||
|
||
var onMotionEvent: ((Int32, Double, Double) -> Void)?
|
||
var onSwipe: ((Double, Double, Double, Double, Int64) -> Void)?
|
||
|
||
private var startX: Double = 0
|
||
private var startY: Double = 0
|
||
private var touchStartTime: TimeInterval = 0
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
isMultipleTouchEnabled = false
|
||
backgroundColor = .clear
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
super.init(coder: coder)
|
||
isMultipleTouchEnabled = false
|
||
backgroundColor = .clear
|
||
}
|
||
|
||
private func relativePoint(of touch: UITouch) -> (Double, Double) {
|
||
let p = touch.location(in: self)
|
||
let w = max(bounds.width, 1)
|
||
let h = max(bounds.height, 1)
|
||
let relX = min(max(Double(p.x / w), 0), 1)
|
||
let relY = min(max(Double(p.y / h), 0), 1)
|
||
return (relX, relY)
|
||
}
|
||
|
||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||
guard let touch = touches.first else { return }
|
||
let (x, y) = relativePoint(of: touch)
|
||
startX = x
|
||
startY = y
|
||
touchStartTime = Date().timeIntervalSince1970
|
||
onMotionEvent?(MotionAction.down, x, y)
|
||
}
|
||
|
||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||
guard let touch = touches.first else { return }
|
||
let (x, y) = relativePoint(of: touch)
|
||
onMotionEvent?(MotionAction.move, x, y)
|
||
}
|
||
|
||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||
guard let touch = touches.first else { return }
|
||
let (x, y) = relativePoint(of: touch)
|
||
onMotionEvent?(MotionAction.up, x, y)
|
||
|
||
let duration = Int64((Date().timeIntervalSince1970 - touchStartTime) * 1000)
|
||
let dx = abs(x - startX)
|
||
let dy = abs(y - startY)
|
||
// 非轻点:补发 SWIPE 高层指令(与 Android 端一致)
|
||
if !(duration < 200 && dx < 0.02 && dy < 0.02) {
|
||
onSwipe?(startX, startY, x, y, duration)
|
||
}
|
||
}
|
||
|
||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||
guard let touch = touches.first else { return }
|
||
let (x, y) = relativePoint(of: touch)
|
||
onMotionEvent?(MotionAction.cancel, x, y)
|
||
}
|
||
}
|
||
|
||
/// SwiftUI 包装:把 UIKit 触控层嵌入 SwiftUI 视图树
|
||
struct TouchOverlay: UIViewRepresentable {
|
||
let onMotionEvent: (Int32, Double, Double) -> Void
|
||
let onSwipe: (Double, Double, Double, Double, Int64) -> Void
|
||
|
||
func makeUIView(context: Context) -> RemoteTouchView {
|
||
let view = RemoteTouchView()
|
||
view.onMotionEvent = onMotionEvent
|
||
view.onSwipe = onSwipe
|
||
return view
|
||
}
|
||
|
||
func updateUIView(_ uiView: RemoteTouchView, context: Context) {
|
||
uiView.onMotionEvent = onMotionEvent
|
||
uiView.onSwipe = onSwipe
|
||
}
|
||
}
|