Last active
June 3, 2020 07:16
-
-
Save KaQuMiQ/51337206c184864e4d0d0ec8ffe0a5d8 to your computer and use it in GitHub Desktop.
Visualize touches
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import UIKit | |
| public final class TouchCaptureWindow: UIWindow { | |
| fileprivate lazy var presentationWindow: TouchPresentationWindow = { [unowned self] in | |
| if #available(iOS 13.0, *) { | |
| guard let scene = self.windowScene else { return TouchPresentationWindow(frame: self.frame) } | |
| return TouchPresentationWindow(windowScene: scene) | |
| } else { | |
| return TouchPresentationWindow(frame: self.frame) | |
| } | |
| }() | |
| public var presentsTouches: Bool = false { | |
| didSet { | |
| guard !presentsTouches else { return } | |
| presentationWindow.ongoingTouches = [] | |
| } | |
| } | |
| override public func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { | |
| guard motion == .motionShake else { return } | |
| presentsTouches.toggle() | |
| } | |
| override public func sendEvent(_ event: UIEvent) { | |
| guard presentsTouches else { return super.sendEvent(event) } | |
| presentationWindow.ongoingTouches = Set<Touch>( | |
| event.allTouches?.compactMap { touch in | |
| switch touch.phase { | |
| case .began, .moved, .stationary: | |
| return Touch(id: touch.hash, location: touch.location(in: self)) | |
| default: | |
| return nil | |
| } | |
| } ?? [] | |
| ) | |
| presentationWindow.setNeedsDisplay() | |
| super.sendEvent(event) | |
| } | |
| } | |
| fileprivate struct Touch: Hashable { | |
| fileprivate static let size: CGSize = CGSize(width: 30, height: 30) | |
| fileprivate static let fillColor: CGColor = { | |
| let color = UIColor.gray.withAlphaComponent(0.4) | |
| return color.cgColor | |
| }() | |
| fileprivate static func pathIn(location: CGPoint) -> CGPath { | |
| CGPath( | |
| ellipseIn: CGRect( | |
| origin: CGPoint( | |
| x: location.x - size.width / 2, | |
| y: location.y - size.height / 2), | |
| size: size), | |
| transform: nil | |
| ) | |
| } | |
| fileprivate let id: Int | |
| fileprivate var path: CGPath | |
| fileprivate init(id: Int, location: CGPoint) { | |
| self.id = id | |
| self.path = Touch.pathIn(location: location) | |
| } | |
| fileprivate func hash(into hasher: inout Hasher) { | |
| hasher.combine(id) | |
| } | |
| fileprivate static func == (_ lhs: Touch, _ rhs: Touch) -> Bool { | |
| lhs.id == rhs.id | |
| } | |
| } | |
| fileprivate final class TouchPresentationWindow: UIWindow { | |
| fileprivate var ongoingTouches: Set<Touch> = Set() { | |
| didSet { | |
| guard oldValue != ongoingTouches else { return } | |
| setNeedsDisplay() | |
| } | |
| } | |
| fileprivate override init(frame: CGRect) { | |
| super.init(frame: frame) | |
| commonInit() | |
| } | |
| @available(iOS 13.0, *) | |
| fileprivate override init(windowScene: UIWindowScene) { | |
| super.init(windowScene: windowScene) | |
| commonInit() | |
| } | |
| @available(*, unavailable) | |
| required fileprivate init?(coder: NSCoder) { fatalError() } | |
| fileprivate func commonInit() { | |
| isUserInteractionEnabled = false | |
| windowLevel = .alert | |
| backgroundColor = .clear | |
| isHidden = false | |
| } | |
| override fileprivate func draw(_ rect: CGRect) { | |
| super.draw(rect) | |
| guard let ctx = UIGraphicsGetCurrentContext() else { return } | |
| UIGraphicsPushContext(ctx) | |
| ctx.setFillColor(Touch.fillColor) | |
| for touch in ongoingTouches { | |
| guard rect.contains(touch.path.boundingBox.center) else { continue } | |
| ctx.addPath(touch.path) | |
| } | |
| ctx.fillPath() | |
| UIGraphicsPopContext() | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use as application main window.