Skip to content
This repository has been archived by the owner on Dec 5, 2022. It is now read-only.

Adding customisable visualisation support for the paws. #57

Merged
merged 3 commits into from
Feb 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions SwiftMonkeyPaws/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Configuration.swift
// SwiftMonkeyPaws
//
// Created by Daniel.Metzing on 11.02.18.
//

import UIKit

public struct Configuration {
// Customise the appearance of the paws
public struct Paws {

/// Define the colour of the Paws
///
/// - randomized: random colour for each paw
/// - constant: same colour for the paws
public enum Color {
case randomized
case constant(UIColor)
}
// Colour of the paws
public let color: Color

// Brightness of a particular paw
public let brightness: CGFloat

// Maximum visible paws at one time
public let maxShown: Int

public init(colour: Color = .randomized, brightness: CGFloat = 0.5, maxShown: Int = 15) {
self.color = colour
self.brightness = brightness
self.maxShown = maxShown
}
}

public struct Radius {

/// Radius of the cross draw upon canceling a touch event
public let cross: CGFloat

/// Radius of the circle draw upon ending a touch event
public let circle: CGFloat

public init(cross: CGFloat = 7, circle: CGFloat = 7) {
self.cross = cross
self.circle = circle
}
}

public let paws: Paws
public let radius: Radius

public init(paws: Paws = Paws(), radius: Radius = Radius()) {
self.paws = paws
self.radius = radius
}
}
37 changes: 37 additions & 0 deletions SwiftMonkeyPaws/MonkeyPawDrawer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// MonkeyPawDrawer.swift
// SwiftMonkeyPaws
//
// Created by Daniel.Metzing on 04.02.18.
//

import UIKit

public final class MonkeyPawDrawer {

public static func monkeyHandPath() -> UIBezierPath {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: -5.91, y: 8.76))
bezierPath.addCurve(to: CGPoint(x: -10.82, y: 2.15), controlPoint1: CGPoint(x: -9.18, y: 7.11), controlPoint2: CGPoint(x: -8.09, y: 4.9))
bezierPath.addCurve(to: CGPoint(x: -16.83, y: -1.16), controlPoint1: CGPoint(x: -13.56, y: -0.6), controlPoint2: CGPoint(x: -14.65, y: 0.5))
bezierPath.addCurve(to: CGPoint(x: -14.65, y: -6.11), controlPoint1: CGPoint(x: -19.02, y: -2.81), controlPoint2: CGPoint(x: -19.57, y: -6.66))
bezierPath.addCurve(to: CGPoint(x: -8.09, y: -2.81), controlPoint1: CGPoint(x: -9.73, y: -5.56), controlPoint2: CGPoint(x: -8.64, y: -0.05))
bezierPath.addCurve(to: CGPoint(x: -11.37, y: -13.82), controlPoint1: CGPoint(x: -7.54, y: -5.56), controlPoint2: CGPoint(x: -7, y: -8.32))
bezierPath.addCurve(to: CGPoint(x: -7.54, y: -17.13), controlPoint1: CGPoint(x: -15.74, y: -19.33), controlPoint2: CGPoint(x: -9.73, y: -20.98))
bezierPath.addCurve(to: CGPoint(x: -4.27, y: -8.87), controlPoint1: CGPoint(x: -5.36, y: -13.27), controlPoint2: CGPoint(x: -6.45, y: -7.76))
bezierPath.addCurve(to: CGPoint(x: -4.27, y: -18.23), controlPoint1: CGPoint(x: -2.08, y: -9.97), controlPoint2: CGPoint(x: -3.72, y: -12.72))
bezierPath.addCurve(to: CGPoint(x: 0.65, y: -18.23), controlPoint1: CGPoint(x: -4.81, y: -23.74), controlPoint2: CGPoint(x: 0.65, y: -25.39))
bezierPath.addCurve(to: CGPoint(x: 1.2, y: -8.32), controlPoint1: CGPoint(x: 0.65, y: -11.07), controlPoint2: CGPoint(x: -0.74, y: -9.29))
bezierPath.addCurve(to: CGPoint(x: 3.93, y: -18.78), controlPoint1: CGPoint(x: 2.29, y: -7.76), controlPoint2: CGPoint(x: 3.93, y: -9.3))
bezierPath.addCurve(to: CGPoint(x: 8.3, y: -16.03), controlPoint1: CGPoint(x: 3.93, y: -23.19), controlPoint2: CGPoint(x: 9.96, y: -21.86))
bezierPath.addCurve(to: CGPoint(x: 5.57, y: -6.11), controlPoint1: CGPoint(x: 7.76, y: -14.1), controlPoint2: CGPoint(x: 3.93, y: -6.66))
bezierPath.addCurve(to: CGPoint(x: 9.4, y: -10.52), controlPoint1: CGPoint(x: 7.21, y: -5.56), controlPoint2: CGPoint(x: 9.16, y: -10.09))
bezierPath.addCurve(to: CGPoint(x: 12.13, y: -6.66), controlPoint1: CGPoint(x: 12.13, y: -15.48), controlPoint2: CGPoint(x: 15.41, y: -9.42))
bezierPath.addCurve(to: CGPoint(x: 8.3, y: -1.16), controlPoint1: CGPoint(x: 8.85, y: -3.91), controlPoint2: CGPoint(x: 8.85, y: -3.91))
bezierPath.addCurve(to: CGPoint(x: 8.3, y: 7.11), controlPoint1: CGPoint(x: 7.76, y: 1.6), controlPoint2: CGPoint(x: 9.4, y: 4.35))
bezierPath.addCurve(to: CGPoint(x: -5.91, y: 8.76), controlPoint1: CGPoint(x: 7.21, y: 9.86), controlPoint2: CGPoint(x: -2.63, y: 10.41))
bezierPath.close()

return bezierPath
}
}
105 changes: 56 additions & 49 deletions SwiftMonkeyPaws/MonkeyPaws.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@

import UIKit

private let maxGesturesShown: Int = 15
private let crossRadius: CGFloat = 7
private let circleRadius: CGFloat = 7

/**
A class that visualises input events as an overlay over
your regular UI. To use, simply instantiate it and
Expand All @@ -35,10 +31,16 @@ private let circleRadius: CGFloat = 7
}
```
*/

public class MonkeyPaws: NSObject, CALayerDelegate {

public typealias BezierPathDrawer = () -> UIBezierPath

private var gestures: [(hash: Int?, gesture: Gesture)] = []
private weak var view: UIView?

let configuration: Configuration
let bezierPathDrawer: BezierPathDrawer
let layer = CALayer()

fileprivate static var tappingTracks: [WeakReference<MonkeyPaws>] = []
Expand All @@ -55,8 +57,18 @@ public class MonkeyPaws: NSObject, CALayerDelegate {
intercept events so that it can visualise them.
If you do not want this, pass `false` here and
provide it with events manually.
- parameter configuration: Configure the visual appearance
of the Monkey paws. By default it uses the built in visual
parameters.
- parameter bezierPathDrawer: Create your own visualisation by
defining a bezier path drawer closure
*/
public init(view: UIView, tapUIApplication: Bool = true) {
public init(view: UIView,
tapUIApplication: Bool = true,
configuration: Configuration = Configuration(),
bezierPathDrawer: @escaping BezierPathDrawer = MonkeyPawDrawer.monkeyHandPath) {
self.configuration = configuration
self.bezierPathDrawer = bezierPathDrawer
super.init()
self.view = view

Expand Down Expand Up @@ -112,14 +124,16 @@ public class MonkeyPaws: NSObject, CALayerDelegate {
gesture.extend(to: point)
}
} else {
if gestures.count > maxGesturesShown {
if gestures.count > configuration.paws.maxShown {
gestures.removeFirst()
}

gestures.append((hash: touchHash, gesture: Gesture(from: point, inLayer: layer)))
gestures.append((hash: touchHash, gesture: Gesture(from: point, inLayer: layer, configuration: configuration, bezierPathDrawer: bezierPathDrawer)))

for i in 0 ..< gestures.count {
gestures[i].gesture.number = gestures.count - i
let number = gestures.count - i
let gesture = gestures[i].gesture
gesture.number = number
}
}
}
Expand Down Expand Up @@ -173,18 +187,21 @@ private class Gesture {
var pathLayer: CAShapeLayer?
var endLayer: CAShapeLayer?

let configuration: Configuration

private static var counter: Int = 0

init(from: CGPoint, inLayer: CALayer) {
init(from: CGPoint, inLayer: CALayer, configuration: Configuration, bezierPathDrawer: @escaping MonkeyPaws.BezierPathDrawer) {
self.points = [from]
self.configuration = configuration

let counter = Gesture.counter
Gesture.counter += 1

let angle = 45 * (CGFloat(fmod(Float(counter) * 0.279, 1)) * 2 - 1)
let mirrored = counter % 2 == 0
let colour = UIColor(hue: CGFloat(fmod(Float(counter) * 0.391, 1)), saturation: 1, brightness: 0.5, alpha: 1)
startLayer.path = monkeyHandPath(angle: angle, scale: 1, mirrored: mirrored).cgPath
let colour: UIColor = pawsColor(configuration: configuration.paws, seed: counter)

startLayer.path = customize(path: bezierPathDrawer(), seed: counter).cgPath

startLayer.strokeColor = colour.cgColor
startLayer.fillColor = nil
startLayer.position = from
Expand All @@ -211,7 +228,7 @@ private class Gesture {
didSet {
numberLayer.string = String(number)

let fraction = Float(number - 1) / Float(maxGesturesShown)
let fraction = Float(number - 1) / Float(configuration.paws.maxShown)
let alpha = sqrt(1 - fraction)
containerLayer.opacity = alpha
}
Expand Down Expand Up @@ -269,7 +286,7 @@ private class Gesture {
layer.fillColor = nil
layer.position = at

let path = circlePath()
let path = circlePath(radius: configuration.radius.circle)
layer.path = path.cgPath

containerLayer.addSublayer(layer)
Expand All @@ -289,56 +306,46 @@ private class Gesture {
layer.fillColor = nil
layer.position = at

let path = crossPath()
let path = crossPath(radius: configuration.radius.cross)
layer.path = path.cgPath

containerLayer.addSublayer(layer)
endLayer = layer
}

func pawsColor(configuration: Configuration.Paws, seed: Int) -> UIColor {
switch configuration.color {
case .randomized:
return UIColor(hue: CGFloat(fmod(Float(seed) * 0.391, 1)),
saturation: 1,
brightness: configuration.brightness,
alpha: 1)
case .constant(let constantColour):
return constantColour.color(WithBrightness: configuration.brightness)
}
}
}

private func monkeyHandPath(angle: CGFloat, scale: CGFloat, mirrored: Bool) -> UIBezierPath {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: -5.91, y: 8.76))
bezierPath.addCurve(to: CGPoint(x: -10.82, y: 2.15), controlPoint1: CGPoint(x: -9.18, y: 7.11), controlPoint2: CGPoint(x: -8.09, y: 4.9))
bezierPath.addCurve(to: CGPoint(x: -16.83, y: -1.16), controlPoint1: CGPoint(x: -13.56, y: -0.6), controlPoint2: CGPoint(x: -14.65, y: 0.5))
bezierPath.addCurve(to: CGPoint(x: -14.65, y: -6.11), controlPoint1: CGPoint(x: -19.02, y: -2.81), controlPoint2: CGPoint(x: -19.57, y: -6.66))
bezierPath.addCurve(to: CGPoint(x: -8.09, y: -2.81), controlPoint1: CGPoint(x: -9.73, y: -5.56), controlPoint2: CGPoint(x: -8.64, y: -0.05))
bezierPath.addCurve(to: CGPoint(x: -11.37, y: -13.82), controlPoint1: CGPoint(x: -7.54, y: -5.56), controlPoint2: CGPoint(x: -7, y: -8.32))
bezierPath.addCurve(to: CGPoint(x: -7.54, y: -17.13), controlPoint1: CGPoint(x: -15.74, y: -19.33), controlPoint2: CGPoint(x: -9.73, y: -20.98))
bezierPath.addCurve(to: CGPoint(x: -4.27, y: -8.87), controlPoint1: CGPoint(x: -5.36, y: -13.27), controlPoint2: CGPoint(x: -6.45, y: -7.76))
bezierPath.addCurve(to: CGPoint(x: -4.27, y: -18.23), controlPoint1: CGPoint(x: -2.08, y: -9.97), controlPoint2: CGPoint(x: -3.72, y: -12.72))
bezierPath.addCurve(to: CGPoint(x: 0.65, y: -18.23), controlPoint1: CGPoint(x: -4.81, y: -23.74), controlPoint2: CGPoint(x: 0.65, y: -25.39))
bezierPath.addCurve(to: CGPoint(x: 1.2, y: -8.32), controlPoint1: CGPoint(x: 0.65, y: -11.07), controlPoint2: CGPoint(x: -0.74, y: -9.29))
bezierPath.addCurve(to: CGPoint(x: 3.93, y: -18.78), controlPoint1: CGPoint(x: 2.29, y: -7.76), controlPoint2: CGPoint(x: 3.93, y: -9.3))
bezierPath.addCurve(to: CGPoint(x: 8.3, y: -16.03), controlPoint1: CGPoint(x: 3.93, y: -23.19), controlPoint2: CGPoint(x: 9.96, y: -21.86))
bezierPath.addCurve(to: CGPoint(x: 5.57, y: -6.11), controlPoint1: CGPoint(x: 7.76, y: -14.1), controlPoint2: CGPoint(x: 3.93, y: -6.66))
bezierPath.addCurve(to: CGPoint(x: 9.4, y: -10.52), controlPoint1: CGPoint(x: 7.21, y: -5.56), controlPoint2: CGPoint(x: 9.16, y: -10.09))
bezierPath.addCurve(to: CGPoint(x: 12.13, y: -6.66), controlPoint1: CGPoint(x: 12.13, y: -15.48), controlPoint2: CGPoint(x: 15.41, y: -9.42))
bezierPath.addCurve(to: CGPoint(x: 8.3, y: -1.16), controlPoint1: CGPoint(x: 8.85, y: -3.91), controlPoint2: CGPoint(x: 8.85, y: -3.91))
bezierPath.addCurve(to: CGPoint(x: 8.3, y: 7.11), controlPoint1: CGPoint(x: 7.76, y: 1.6), controlPoint2: CGPoint(x: 9.4, y: 4.35))
bezierPath.addCurve(to: CGPoint(x: -5.91, y: 8.76), controlPoint1: CGPoint(x: 7.21, y: 9.86), controlPoint2: CGPoint(x: -2.63, y: 10.41))
bezierPath.close()

bezierPath.apply(CGAffineTransform(translationX: 0.5, y: 0))

bezierPath.apply(CGAffineTransform(scaleX: scale, y: scale))
private func customize(path: UIBezierPath, seed: Int) -> UIBezierPath {

let angle = 45 * (CGFloat(fmod(Float(seed) * 0.279, 1)) * 2 - 1)
let mirrored = seed % 2 == 0

if mirrored {
bezierPath.apply(CGAffineTransform(scaleX: -1, y: 1))
path.apply(CGAffineTransform(scaleX: -1, y: 1))
}

bezierPath.apply(CGAffineTransform(rotationAngle: angle / 180 * CGFloat.pi))
path.apply(CGAffineTransform(rotationAngle: angle / 180 * CGFloat.pi))

return bezierPath
return path
}

private func circlePath() -> UIBezierPath {
return UIBezierPath(ovalIn: CGRect(centre: CGPoint.zero, size: CGSize(width: circleRadius * 2, height: circleRadius * 2)))
private func circlePath(radius: CGFloat) -> UIBezierPath {
return UIBezierPath(ovalIn: CGRect(centre: CGPoint.zero, size: CGSize(width: radius * 2, height: radius * 2)))
}

private func crossPath() -> UIBezierPath {
let rect = CGRect(centre: CGPoint.zero, size: CGSize(width: crossRadius * 2, height: crossRadius * 2))
private func crossPath(radius: CGFloat) -> UIBezierPath {
let rect = CGRect(centre: CGPoint.zero, size: CGSize(width: radius * 2, height: radius * 2))
let cross = UIBezierPath()
cross.move(to: CGPoint(x: rect.minX, y: rect.minY))
cross.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
Expand Down
12 changes: 12 additions & 0 deletions SwiftMonkeyPaws/SwiftMonkeyPaws.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
objects = {

/* Begin PBXBuildFile section */
1810357020337681005D6D35 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1810356E20337680005D6D35 /* Configuration.swift */; };
1810357120337681005D6D35 /* UIColor+MonkeyPaws.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1810356F20337681005D6D35 /* UIColor+MonkeyPaws.swift */; };
189807B920279AD800B9F544 /* MonkeyPawDrawer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189807B820279AD800B9F544 /* MonkeyPawDrawer.swift */; };
C929B55C1DD0B7C9004B256F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C929B55B1DD0B7C9004B256F /* UIKit.framework */; };
OBJ_18 /* MonkeyPaws.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* MonkeyPaws.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
1810356E20337680005D6D35 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = SOURCE_ROOT; };
1810356F20337681005D6D35 /* UIColor+MonkeyPaws.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+MonkeyPaws.swift"; sourceTree = SOURCE_ROOT; };
189807B820279AD800B9F544 /* MonkeyPawDrawer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonkeyPawDrawer.swift; sourceTree = SOURCE_ROOT; };
C929B55B1DD0B7C9004B256F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
C943263D1DDB123A0038A891 /* SwiftMonkeyPaws.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = SwiftMonkeyPaws.podspec; path = ../SwiftMonkeyPaws.podspec; sourceTree = "<group>"; };
OBJ_12 /* SwiftMonkeyPaws.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftMonkeyPaws.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -78,6 +84,9 @@
isa = PBXGroup;
children = (
OBJ_9 /* MonkeyPaws.swift */,
1810356E20337680005D6D35 /* Configuration.swift */,
189807B820279AD800B9F544 /* MonkeyPawDrawer.swift */,
1810356F20337681005D6D35 /* UIColor+MonkeyPaws.swift */,
);
name = SwiftMonkeyPaws;
path = .;
Expand Down Expand Up @@ -138,6 +147,9 @@
buildActionMask = 0;
files = (
OBJ_18 /* MonkeyPaws.swift in Sources */,
1810357020337681005D6D35 /* Configuration.swift in Sources */,
1810357120337681005D6D35 /* UIColor+MonkeyPaws.swift in Sources */,
189807B920279AD800B9F544 /* MonkeyPawDrawer.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
26 changes: 26 additions & 0 deletions SwiftMonkeyPaws/UIColor+MonkeyPaws.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// UIColor+MonkeyPaws.swift
// SwiftMonkeyPaws
//
// Created by Daniel.Metzing on 01.02.18.
//

import UIKit

extension UIColor {
func color(WithBrightness brightness: CGFloat) -> UIColor {
var H: CGFloat = 0
var S: CGFloat = 0
var B: CGFloat = 0
var A: CGFloat = 0

guard getHue(&H, saturation: &S, brightness: &B, alpha: &A) else {
return self
}

B += (brightness - 1.0)
B = max(min(B, 1.0), 0.0)

return UIColor(hue: H, saturation: S, brightness: B, alpha: A)
}
}