Skip to content

Instantly share code, notes, and snippets.

@KaQuMiQ
Last active June 3, 2020 07:16
Show Gist options
  • Select an option

  • Save KaQuMiQ/51337206c184864e4d0d0ec8ffe0a5d8 to your computer and use it in GitHub Desktop.

Select an option

Save KaQuMiQ/51337206c184864e4d0d0ec8ffe0a5d8 to your computer and use it in GitHub Desktop.
Visualize touches
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()
}
}
@KaQuMiQ
Copy link
Author

KaQuMiQ commented Jun 2, 2020

Use as application main window.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment