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

Conversation

dirtydanee
Copy link

Issue #9, creating possibility to define custom visualisation. Customisable properties:

  • look of the "paw" by injecting custom bezier path
  • colour, brightness and the maximum number of the paws being shown
  • the radiuses of the circle and cross elements

Default values are all falling back to the already defined ones. Let me know if you see anything how i could improve my PR.

@wojciechczerski
Copy link
Member

You have made an awesome work here @dirtydanee! Thank you for your effort!
I have some minor change requests which I hope won't be difficult to fix.

@dirtydanee
Copy link
Author

@wojciechczerski just hit me with the list you would like me to change.

///
/// - randomized: random colour for each paw
/// - constant: same colour for the paws
enum Colour {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be public.


public struct Configuration {
// Customise the appearance of the paws
public struct Paws {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default struct initializer is internal therefore it's not possible to instantiate this object without an explicit public init() defined.

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

public static let `default`: Paws = Paws(colour: .randomized,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add the public init() for Paws you might as well provide default parameter values, like that:

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

The result is that writing Paws.default is equal to Paws() but with default initializer parameter values I think the usage is slightly more convenient. What do you think?

maxShown: 15)
}

public struct Radius {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public init(...) should be added.

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

public static let `default`: Radius = Radius(cross: 7,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider replacing with a parameterless public initializer with all parameters having default values (just like it was suggested for the Paws object).

let paws: Paws
let radius: Radius

public static let `default`: MonkeyPaws.Configuration = MonkeyPaws.Configuration(paws: Paws.default, radius: Radius.default)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider replacing with a parameterless public initializer with all parameters having default values (just like it was suggested for the Paws object).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense.

public class MonkeyPaws: NSObject, CALayerDelegate {

public struct Configuration {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move this struct to a separate file? If you want the usage to stay the same (MonkeyPaws.Configuration) it can be added as an extension:

extension MonkeyPaws {
    public struct Configuration {
        ...
    }
}

What do you think?

@wojciechczerski
Copy link
Member

@dirtydanee If you will be fixing any of the PR comments you might just force-push the branch with new changes ;) Or if you prefer add some 'fix review comment' commits 😄

@dirtydanee
Copy link
Author

@wojciechczerski my latest commit includes all the changes from your review 🤜🤛


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move the color computation to a separate function? For example:

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

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

bezierPath.apply(CGAffineTransform(scaleX: scale, y: scale))
private func customizePath(_ path: UIBezierPath, angle: CGFloat, scale: CGFloat, mirrored: Bool) -> UIBezierPath {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scale parameter is always 1 therefore I would just remove it. Also the remaining angle and mirrored could be computed inside based on a seed parameter (equal to Gesture.counter):

func customize(path: UIBezierPath, seed: Int) -> UIBezierPath 

Would it make sense? We could then remove this code from the init:

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


bezierPath.apply(CGAffineTransform(scaleX: scale, y: scale))
private func customizePath(_ path: UIBezierPath, angle: CGFloat, scale: CGFloat, mirrored: Bool) -> UIBezierPath {
path.apply(CGAffineTransform(translationX: 0.5, y: 0))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess not. removed.

private func customizePath(_ path: UIBezierPath, angle: CGFloat, scale: CGFloat, mirrored: Bool) -> UIBezierPath {
path.apply(CGAffineTransform(translationX: 0.5, y: 0))

path.apply(CGAffineTransform(scaleX: scale, y: scale))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like mentioned before, the scale is always 1 therefore I would just remove it.


import UIKit

extension MonkeyPaws {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I see that putting it in an extension was a bad idea. I like the separate file but when you start to create your custom configuration you immediately see the annoying repeated. For example instead of this:

let pawsConfig = Configuration.Paws(colour: .constant(.cyan), brightness: 0.5, maxShown: 3)
let radiusConfig = Configuration.Radius(cross: 10, circle: 10)
let config = Configuration(paws: pawsConfig, radius: radiusConfig)

you have to write:

let pawsConfig = MonkeyPaws.Configuration.Paws(colour: .constant(.cyan), brightness: 0.5, maxShown: 3)
let radiusConfig = MonkeyPaws.Configuration.Radius(cross: 10, circle: 10)
let config = MonkeyPaws.Configuration(paws: pawsConfig, radius: radiusConfig)

So, another idea: how about we move the Configuration struct to a separate Configuration.swift file? It shouldn't be in the Extensions folder but in the same folder where MonkeyPaws.swift is. And it will be just Configuration and not MonkeyPaws.Configuration. What do you think?

Copy link
Author

@dirtydanee dirtydanee Feb 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to create namespaces, and for this configuration tight coupling with MonkeyPaws makes sense to me. However, going below 2 levels is not really encouraged, like MonkeyPaws.Configuration.Paws, so i understand the frustration. I will change it as requested.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self not needed.

Copy link
Author

@dirtydanee dirtydanee Feb 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My rule of thumb with the usage of self is if a variable declared on the scope of the class, it must have the self prefix. I think this is a good rule, and it helps to avoid inconsistent usage of self.

But i understand you guys have a different coding style, happy to adjust.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes more from using SwiftFormat that automatically removes or extra self usages. I must definitely include it somehow in the build process so that the consistency check is done (and applied) automatically.

@@ -211,9 +241,9 @@ private class Gesture {
didSet {
numberLayer.string = String(number)

let fraction = Float(number - 1) / Float(maxGesturesShown)
let fraction = Float(number - 1) / Float(self.configuration.paws.maxShown)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self not needed.

let alpha = sqrt(1 - fraction)
containerLayer.opacity = alpha
self.containerLayer.opacity = alpha
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self not needed.

@@ -269,7 +299,7 @@ private class Gesture {
layer.fillColor = nil
layer.position = at

let path = circlePath()
let path = circlePath(radius: self.configuration.radius.circle)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self not needed.

@@ -289,56 +319,34 @@ private class Gesture {
layer.fillColor = nil
layer.position = at

let path = crossPath()
let path = crossPath(radius: self.configuration.radius.cross)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self not needed.

@wojciechczerski
Copy link
Member

Thank you @dirtydanee for your work and for patiently fixing my picky comments ;) I'll try to somehow automate formatting and style checks in the future, so that it is more convenient when somebody wants to contribute.

In the mean time, expect a new release soon! 😃

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants