[fix] prevent iOS relative cursor jumps after movement

This commit is contained in:
dijunkun
2026-09-07 21:11:27 +08:00
parent 5d97d5a393
commit 2d3f1bb475
3 changed files with 21 additions and 15 deletions
@@ -58,6 +58,7 @@ struct RemoteTouchInputView: UIViewRepresentable {
let videoSize: CGSize
let controlMode: MouseControlMode
let remoteCursorPosition: CGPoint?
let remoteCursorPositionRevision: UInt64
let viewportScale: CGFloat
let viewportOffset: CGSize
let viewportChanged: (CGFloat, CGSize) -> Void
@@ -81,7 +82,8 @@ struct RemoteTouchInputView: UIViewRepresentable {
view.onLeftUp = leftUp
view.onRightClick = rightClick
view.onScroll = scroll
view.synchronizeRemoteCursor(remoteCursorPosition)
view.synchronizeRemoteCursor(remoteCursorPosition,
revision: remoteCursorPositionRevision)
}
}
@@ -108,6 +110,7 @@ final class RemoteTouchSurface: UIView, UIGestureRecognizerDelegate {
private var relativeCursorPoint: (Float, Float) = (0.5, 0.5)
private var lastRemoteCursorPoint: (Float, Float)?
private var lastRemoteCursorRevision: UInt64?
private var relativePanActive = false
private var heldDragPoint: (Float, Float)?
private var heldDragLastLocation: CGPoint?
@@ -184,7 +187,12 @@ final class RemoteTouchSurface: UIView, UIGestureRecognizerDelegate {
viewportOffset = constrainedViewportOffset(offset, scale: nextScale)
}
func synchronizeRemoteCursor(_ position: CGPoint?) {
func synchronizeRemoteCursor(_ position: CGPoint?, revision: UInt64) {
// SwiftUI refreshes this view for video frames and shape-only feedback
// too. Consume each position update once, even when it arrives during
// local movement, so a later refresh cannot restore an old coordinate.
guard revision != lastRemoteCursorRevision else { return }
lastRemoteCursorRevision = revision
guard let position else {
lastRemoteCursorPoint = nil
return
@@ -194,7 +202,11 @@ final class RemoteTouchSurface: UIView, UIGestureRecognizerDelegate {
Float(min(max(position.y, 0), 1))
)
lastRemoteCursorPoint = point
applyRemoteCursorIfIdle()
guard controlMode == .relative,
!relativePanActive,
heldDragPoint == nil,
hoverLastLocation == nil else { return }
relativeCursorPoint = point
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
@@ -400,7 +412,6 @@ final class RemoteTouchSurface: UIView, UIGestureRecognizerDelegate {
}
default:
relativePanActive = false
applyRemoteCursorIfIdle()
}
}
@@ -429,7 +440,6 @@ final class RemoteTouchSurface: UIView, UIGestureRecognizerDelegate {
}
heldDragPoint = nil
heldDragLastLocation = nil
applyRemoteCursorIfIdle()
default:
break
}
@@ -457,14 +467,6 @@ final class RemoteTouchSurface: UIView, UIGestureRecognizerDelegate {
return relativeCursorPoint
}
private func applyRemoteCursorIfIdle() {
guard controlMode == .relative,
!relativePanActive,
heldDragPoint == nil,
let point = lastRemoteCursorPoint else { return }
relativeCursorPoint = point
}
private func normalized(_ point: CGPoint) -> (Float, Float)? {
guard let renderRect = renderRect(), renderRect.contains(point) else { return nil }
return (Float((point.x - renderRect.minX) / renderRect.width),
@@ -381,6 +381,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
@Published private(set) var remoteCursorVisible = true
@Published private(set) var remoteCursorShape = 0
@Published private(set) var remoteCursorPosition: CGPoint?
@Published private(set) var remoteCursorPositionRevision: UInt64 = 0
@Published private(set) var remoteCursorVisualOffset = CGPoint.zero
@Published private(set) var hasRemoteCursorState = false
@Published private(set) var bitrate: UInt = 0
@@ -775,6 +776,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
remoteCursorVisible = true
remoteCursorShape = 0
remoteCursorPosition = nil
remoteCursorPositionRevision &+= 1
remoteCursorVisualOffset = .zero
hasRemoteCursorState = false
remoteCursorSequence = nil
@@ -991,6 +993,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
} else {
remoteCursorPosition = nil
}
remoteCursorPositionRevision &+= 1
}
hasRemoteCursorState = true
}
@@ -79,6 +79,7 @@ struct RemoteSessionView: View {
videoSize: session.displayGeometrySize,
controlMode: session.mouseControlMode,
remoteCursorPosition: session.remoteCursorPosition,
remoteCursorPositionRevision: session.remoteCursorPositionRevision,
viewportScale: viewport.scale,
viewportOffset: viewport.offset,
viewportChanged: { scale, offset in
@@ -161,8 +162,8 @@ struct RemoteSessionView: View {
.onChange(of: session.mouseControlMode) { _ in
cursorPosition = session.remoteCursorPosition
}
.onChange(of: session.remoteCursorPosition) { position in
guard let position else { return }
.onChange(of: session.remoteCursorPositionRevision) { _ in
guard let position = session.remoteCursorPosition else { return }
cursorPosition = position
}
.fileImporter(isPresented: $showingFileImporter,