Skip to content

Commit

Permalink
Merge pull request #27 from p-x9/feature/propagate-other-window-events
Browse files Browse the repository at this point in the history
Add option to propagate touch events to other windows
  • Loading branch information
p-x9 authored Sep 4, 2023
2 parents adff033 + 84bf089 commit f556c56
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 45 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}
```


#### Propagate touch events to other windows
The following configuration allows you to share a touch event received in one window with other windows.

```swift
v.shouldPropagateEventAcrossWindows = true
```

In the given configuration, even when windows overlap, typically only one window receives a touch event.
Moreover, if a touch event starts in one window, such as Window A, it will only be responsive in Window A, even if it moves to another window, like Window B.

Therefore, the touch event can be shared so that the marker of the tapped point will be displayed in the other window even in such a case.


## License
TouchTracker is released under the MIT License. See [LICENSE](./LICENSE)
6 changes: 2 additions & 4 deletions Sources/TouchTracker/Cocoa/TouchPointUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ class TouchPointUIView: UIWindow {
self.image = image
self.isShowLocation = isShowLocation

let frame = CGRect(origin: location,
let frame = CGRect(origin: .zero,
size: .init(width: radius * 2, height: radius * 2))
super.init(frame: frame)

setupViews()
setupViewConstraints()

windowLevel = .statusBar
windowLevel = .init(10000005)
isUserInteractionEnabled = false
}

Expand Down Expand Up @@ -121,8 +121,6 @@ class TouchPointUIView: UIWindow {
}

func setupViewConstraints() {
translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
self.heightAnchor.constraint(equalToConstant: radius * 2),
self.widthAnchor.constraint(equalToConstant: radius * 2),
Expand Down
72 changes: 46 additions & 26 deletions Sources/TouchTracker/Cocoa/TouchTrackingUIView.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//
// CocoaTrackPointManager.swift
//
//
//
// Created by p-x9 on 2023/04/14.
//
//
//

#if canImport(UIKit)
Expand Down Expand Up @@ -43,6 +43,10 @@ public class TouchTrackingUIView: UIView {
/// display mode of touched points.
public var displayMode: DisplayMode

/// A boolean value that indicates whether the event should propagate across windows.
/// If set to `true`, the touch events received in one window will also be shared
/// with other windows when applicable.
public var shouldPropagateEventAcrossWindows: Bool = false

var touches: Set<UITouch> = []
var locations: [CGPoint] = [] {
Expand All @@ -55,7 +59,7 @@ public class TouchTrackingUIView: UIView {
public var isEnabled: Bool = true

var pointWindows = [TouchPointUIView]()

/// initializer
/// - Parameters:
/// - radius: radius of mark on touched point
Expand Down Expand Up @@ -134,25 +138,6 @@ public class TouchTrackingUIView: UIView {
}
}

public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.formUnion(touches)
updateLocations()
}

public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
updateLocations()
}

public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.subtract(touches)
updateLocations()
}

public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.subtract(touches)
updateLocations()
}

func updateLocations() {
if !isEnabled {
self.touches = []
Expand All @@ -179,6 +164,7 @@ public class TouchTrackingUIView: UIView {
pointWindows[touches.count..<pointWindows.count].forEach {
$0.isHidden = true
$0.windowScene = nil
$0.removeFromSuperview()
}
pointWindows = Array(pointWindows[0..<touches.count])
}
Expand Down Expand Up @@ -207,13 +193,47 @@ public class TouchTrackingUIView: UIView {
zip(pointWindows, locations).forEach { window, location in
window.location = location
window.center = .init(x: location.x + offset.x,
y: location.y + offset.y)
window.windowScene = self.window?.windowScene
// WORKAROUND: Apply changes of orientation
window.rootViewController = .init()
y: location.y + offset.y)

if shouldPropagateEventAcrossWindows,
let screen = self.window?.screen,
let keyboardScene = UIWindowScene.keyboardScene(for: screen),
let keyboardRemoteWindow = keyboardScene.allWindows.first {
keyboardRemoteWindow.addSubview(window)
} else {
// WORKAROUND: Apply changes of orientation
window.rootViewController = .init()
window.windowScene = self.window?.windowScene
}

window.isHidden = false
}
}
}

extension TouchTrackingUIView: TouchTrackable {
func touchesBegan(_ touches: Set<UITouch>, with receiver: UIWindow) {
if !shouldPropagateEventAcrossWindows && window != receiver {
return
}
self.touches.formUnion(touches)
updateLocations()
}

func touchesMoved(_ touches: Set<UITouch>, with receiver: UIWindow) {
if !shouldPropagateEventAcrossWindows && window != receiver {
return
}
updateLocations()
}

func touchesEndedOrCancelled(_ touches: Set<UITouch>, with receiver: UIWindow) {
if !shouldPropagateEventAcrossWindows && window != receiver {
return
}
self.touches.subtract(touches)
updateLocations()
}
}

#endif
16 changes: 12 additions & 4 deletions Sources/TouchTracker/Extension/UIWindow+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ extension UIWindow {
let moved = touches.filter { $0.phase == .moved }
let ended = touches.filter { $0.phase == .cancelled || $0.phase == .ended }

let touchLocationViews: [UIView] = find(for: TouchLocationCocoaView.self) + find(for: TouchTrackingUIView.self)
let touchLocationViews: [any TouchTrackable] = UIApplication.shared.connectedScenes
.lazy
.compactMap { $0 as? UIWindowScene }
.reduce([]) { partialResult, scene in
partialResult + scene.windows
}
.reduce([]) { partialResult, window in
partialResult + window.find(for: TouchLocationCocoaView.self) + window.find(for: TouchTrackingUIView.self)
}

touchLocationViews
.filter {
Expand All @@ -56,13 +64,13 @@ extension UIWindow {
}
.forEach { view in
if !began.isEmpty {
view.touchesBegan(began, with: event)
view.touchesBegan(began, with: self)
}
if !moved.isEmpty {
view.touchesMoved(moved, with: event)
view.touchesMoved(moved, with: self)
}
if !ended.isEmpty {
view.touchesEnded(ended, with: event)
view.touchesEndedOrCancelled(ended, with: self)
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions Sources/TouchTracker/Extension/UIWindowScene+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// UIWindowScene+.swift
//
//
// Created by p-x9 on 2023/09/05.
//
//

#if canImport(UIKit)
import UIKit

extension UIWindowScene {
static func keyboardScene(for screen: UIScreen) -> UIWindowScene? {
// _keyboardWindowSceneForScreen:create:
let obfuString = [":etaer", "c:nee", "rcSroF", "e", "necSwod", "niWdraobyek_"]
let name = String(obfuString.joined(separator: "").reversed())

let selector = Selector(name)
guard UIWindowScene.responds(to: selector),
let scene = UIWindowScene.perform(selector, with: screen, with: false).takeUnretainedValue() as? UIWindowScene else {
return nil
}

return scene
}

var allWindows: [UIWindow] {
// _allWindows
let name = String(["_", "al", "lWin", "dows"].joined(separator: ""))
let selector = Selector(name)
guard self.responds(to: selector),
let windows = self.perform(selector).takeUnretainedValue() as? [UIWindow] else {
return []
}

return windows
}
}

#endif
19 changes: 8 additions & 11 deletions Sources/TouchTracker/TouchLocationCocoaView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,24 @@ class TouchLocationCocoaView: UIView {
fatalError("init(coder:) has not been implemented")
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.formUnion(touches)
self.locations = self.touches.map { $0.location(in: self) }
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
false
}
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
extension TouchLocationCocoaView: TouchTrackable {
func touchesBegan(_ touches: Set<UITouch>, with receiver: UIWindow) {
self.touches.formUnion(touches)
self.locations = self.touches.map { $0.location(in: self) }
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.subtract(touches)
func touchesMoved(_ touches: Set<UITouch>, with receiver: UIWindow) {
self.locations = self.touches.map { $0.location(in: self) }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
func touchesEndedOrCancelled(_ touches: Set<UITouch>, with receiver: UIWindow) {
self.touches.subtract(touches)
self.locations = self.touches.map { $0.location(in: self) }
}

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
false
}
}
#endif
18 changes: 18 additions & 0 deletions Sources/TouchTracker/TouchTrackable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// TouchTrackable.swift
//
//
// Created by p-x9 on 2023/09/03.
//
//

#if canImport(UIKit)
import UIKit

protocol TouchTrackable: UIView {
func touchesBegan(_ touches: Set<UITouch>, with receiver: UIWindow)
func touchesMoved(_ touches: Set<UITouch>, with receiver: UIWindow)
func touchesEndedOrCancelled(_ touches: Set<UITouch>, with receiver: UIWindow)
}

#endif

0 comments on commit f556c56

Please sign in to comment.