From 16b9f6aa6c460f2040993d601612a7acfe53ea27 Mon Sep 17 00:00:00 2001 From: Jonathan Cardasis Date: Sun, 8 Sep 2019 19:16:58 -0400 Subject: [PATCH] Updated and improved tests. Fixed sync layout issue and updated UIControlEvents to events that make more sense. Added tests for brightness slider. --- ChromaColorPicker.xcodeproj/project.pbxproj | 28 +- README.md | 11 +- Source/ChromaBrightnessSlider.swift | 106 ++++---- Source/ChromaColorPicker.swift | 10 +- Source/ColorWheelView.swift | 1 + Source/Extensions/UIView+Utils.swift | 18 ++ .../ChromaColorHandle.swift | 10 +- .../SliderHandleView.swift | 18 +- .../SliderTrackView.swift | 0 Tests/ChromaBrightnessSliderTests.swift | 251 ++++++++++++++++++ Tests/ChromaColorPickerTests.swift | 56 +--- Tests/Helpers/SliderTrackViewTests.swift | 9 - .../ChromaColorHandleTests.swift | 0 .../SliderHandleViewTests.swift | 1 - Tests/TestHelpers/FakeTouch.swift | 37 +++ 15 files changed, 414 insertions(+), 142 deletions(-) create mode 100644 Source/Extensions/UIView+Utils.swift rename Source/{Helpers => Supporting Views}/ChromaColorHandle.swift (96%) rename Source/{Helpers => Supporting Views}/SliderHandleView.swift (80%) rename Source/{Helpers => Supporting Views}/SliderTrackView.swift (100%) create mode 100644 Tests/ChromaBrightnessSliderTests.swift delete mode 100644 Tests/Helpers/SliderTrackViewTests.swift rename Tests/{Helpers => Supporting Views}/ChromaColorHandleTests.swift (100%) rename Tests/{Helpers => Supporting Views}/SliderHandleViewTests.swift (99%) create mode 100644 Tests/TestHelpers/FakeTouch.swift diff --git a/ChromaColorPicker.xcodeproj/project.pbxproj b/ChromaColorPicker.xcodeproj/project.pbxproj index ec4d0d6..9705c06 100644 --- a/ChromaColorPicker.xcodeproj/project.pbxproj +++ b/ChromaColorPicker.xcodeproj/project.pbxproj @@ -23,6 +23,9 @@ FC4387CB22625DA800F739F1 /* SliderHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4387CA22625DA800F739F1 /* SliderHandleView.swift */; }; FC4387CD2262B82600F739F1 /* ChromaControlStylable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4387CC2262B82600F739F1 /* ChromaControlStylable.swift */; }; FC4387DE226974FD00F739F1 /* UIColor+Brightness.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4387DC226972EB00F739F1 /* UIColor+Brightness.swift */; }; + FC89B7E02325B07F00D007AB /* ChromaBrightnessSliderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC89B7DF2325B07F00D007AB /* ChromaBrightnessSliderTests.swift */; }; + FC89B7E22325B8B900D007AB /* FakeTouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC89B7E12325B8B900D007AB /* FakeTouch.swift */; }; + FC89B7E42325C24F00D007AB /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC89B7E32325C24F00D007AB /* UIView+Utils.swift */; }; FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */; }; FCCA42A7226023F000BE2FF9 /* ChromaColorHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */; }; FCCA42AA2260329900BE2FF9 /* UIView+DropShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A92260329900BE2FF9 /* UIView+DropShadow.swift */; }; @@ -30,7 +33,6 @@ FCE07C01227B4C8B00920217 /* UIColor+TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE07C00227B4C8B00920217 /* UIColor+TestHelpers.swift */; }; FCE07C04227B4DB900920217 /* UIView+DropShadowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE07C03227B4DB900920217 /* UIView+DropShadowTests.swift */; }; FCE07C06227B525000920217 /* ChromaControlStylableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE07C05227B525000920217 /* ChromaControlStylableTests.swift */; }; - FCE07C08228A0F5C00920217 /* SliderTrackViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE07C07228A0F5C00920217 /* SliderTrackViewTests.swift */; }; FCE07C0A228A0F6800920217 /* SliderHandleViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE07C09228A0F6800920217 /* SliderHandleViewTests.swift */; }; FCE07C0C228A0F7500920217 /* ChromaColorHandleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE07C0B228A0F7500920217 /* ChromaColorHandleTests.swift */; }; FCEA4E272235AAA200C0A1B6 /* ChromaColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */; }; @@ -93,6 +95,9 @@ FC4387CA22625DA800F739F1 /* SliderHandleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderHandleView.swift; sourceTree = ""; }; FC4387CC2262B82600F739F1 /* ChromaControlStylable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaControlStylable.swift; sourceTree = ""; }; FC4387DC226972EB00F739F1 /* UIColor+Brightness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Brightness.swift"; sourceTree = ""; }; + FC89B7DF2325B07F00D007AB /* ChromaBrightnessSliderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaBrightnessSliderTests.swift; sourceTree = ""; }; + FC89B7E12325B8B900D007AB /* FakeTouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeTouch.swift; sourceTree = ""; }; + FC89B7E32325C24F00D007AB /* UIView+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = ""; }; FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWheelView.swift; sourceTree = ""; }; FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorHandle.swift; sourceTree = ""; }; FCCA42A92260329900BE2FF9 /* UIView+DropShadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+DropShadow.swift"; sourceTree = ""; }; @@ -101,7 +106,6 @@ FCE07C00227B4C8B00920217 /* UIColor+TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+TestHelpers.swift"; sourceTree = ""; }; FCE07C03227B4DB900920217 /* UIView+DropShadowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+DropShadowTests.swift"; sourceTree = ""; }; FCE07C05227B525000920217 /* ChromaControlStylableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaControlStylableTests.swift; sourceTree = ""; }; - FCE07C07228A0F5C00920217 /* SliderTrackViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderTrackViewTests.swift; sourceTree = ""; }; FCE07C09228A0F6800920217 /* SliderHandleViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderHandleViewTests.swift; sourceTree = ""; }; FCE07C0B228A0F7500920217 /* ChromaColorHandleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorHandleTests.swift; sourceTree = ""; }; FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorPicker.swift; sourceTree = ""; }; @@ -137,7 +141,7 @@ 3503B8301F2689BC00750356 /* Source */ = { isa = PBXGroup; children = ( - FC4387C722625C5F00F739F1 /* Helpers */, + FC4387C722625C5F00F739F1 /* Supporting Views */, FCCA42A82260325F00BE2FF9 /* Extensions */, 3503B8311F2689BC00750356 /* ChromaColorPicker.h */, 3503B8321F2689BC00750356 /* Info.plist */, @@ -186,9 +190,10 @@ isa = PBXGroup; children = ( FCE07BFF227B4C7A00920217 /* TestHelpers */, - FCE07C02227B4D9500920217 /* Helpers */, + FCE07C02227B4D9500920217 /* Supporting Views */, FCCA42AD226038BD00BE2FF9 /* Extensions */, FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerTests.swift */, + FC89B7DF2325B07F00D007AB /* ChromaBrightnessSliderTests.swift */, FCCA42AB226038A400BE2FF9 /* ColorWheelViewTests.swift */, FCE07C05227B525000920217 /* ChromaControlStylableTests.swift */, FC1BD8C12207D7B700817AF3 /* Info.plist */, @@ -196,14 +201,14 @@ path = Tests; sourceTree = ""; }; - FC4387C722625C5F00F739F1 /* Helpers */ = { + FC4387C722625C5F00F739F1 /* Supporting Views */ = { isa = PBXGroup; children = ( FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */, FC4387CA22625DA800F739F1 /* SliderHandleView.swift */, FC4387C822625C7000F739F1 /* SliderTrackView.swift */, ); - path = Helpers; + path = "Supporting Views"; sourceTree = ""; }; FCCA42A82260325F00BE2FF9 /* Extensions */ = { @@ -211,6 +216,7 @@ children = ( FC4387DC226972EB00F739F1 /* UIColor+Brightness.swift */, FCCA42A92260329900BE2FF9 /* UIView+DropShadow.swift */, + FC89B7E32325C24F00D007AB /* UIView+Utils.swift */, ); path = Extensions; sourceTree = ""; @@ -228,18 +234,18 @@ isa = PBXGroup; children = ( FCE07C00227B4C8B00920217 /* UIColor+TestHelpers.swift */, + FC89B7E12325B8B900D007AB /* FakeTouch.swift */, ); path = TestHelpers; sourceTree = ""; }; - FCE07C02227B4D9500920217 /* Helpers */ = { + FCE07C02227B4D9500920217 /* Supporting Views */ = { isa = PBXGroup; children = ( FCE07C0B228A0F7500920217 /* ChromaColorHandleTests.swift */, FCE07C09228A0F6800920217 /* SliderHandleViewTests.swift */, - FCE07C07228A0F5C00920217 /* SliderTrackViewTests.swift */, ); - path = Helpers; + path = "Supporting Views"; sourceTree = ""; }; /* End PBXGroup section */ @@ -395,6 +401,7 @@ files = ( FCEA4E272235AAA200C0A1B6 /* ChromaColorPicker.swift in Sources */, FC4387CD2262B82600F739F1 /* ChromaControlStylable.swift in Sources */, + FC89B7E42325C24F00D007AB /* UIView+Utils.swift in Sources */, FCCA42AA2260329900BE2FF9 /* UIView+DropShadow.swift in Sources */, FC4387DE226974FD00F739F1 /* UIColor+Brightness.swift in Sources */, FC4387C62262556600F739F1 /* ChromaBrightnessSlider.swift in Sources */, @@ -419,11 +426,12 @@ buildActionMask = 2147483647; files = ( FCE07C06227B525000920217 /* ChromaControlStylableTests.swift in Sources */, + FC89B7E02325B07F00D007AB /* ChromaBrightnessSliderTests.swift in Sources */, + FC89B7E22325B8B900D007AB /* FakeTouch.swift in Sources */, FCE07C0C228A0F7500920217 /* ChromaColorHandleTests.swift in Sources */, FC1BD8C02207D7B700817AF3 /* ChromaColorPickerTests.swift in Sources */, FCE07BFE227B4A8100920217 /* UIColor+BrightnessTests.swift in Sources */, FCE07C0A228A0F6800920217 /* SliderHandleViewTests.swift in Sources */, - FCE07C08228A0F5C00920217 /* SliderTrackViewTests.swift in Sources */, FC4387C422603AE900F739F1 /* ColorWheelViewTests.swift in Sources */, FCE07C01227B4C8B00920217 /* UIColor+TestHelpers.swift in Sources */, FCE07C04227B4DB900920217 /* UIView+DropShadowTests.swift in Sources */, diff --git a/README.md b/README.md index 142bd8c..61e34da 100644 --- a/README.md +++ b/README.md @@ -78,17 +78,18 @@ addTarget(self, action: #selector(sliderDidValueChange(_:)), for: .valueChanged) _ChromaColorPicker_ | Event | Description | | :-----------------:|:-------------| -| `.touchDown` | Called when a handle is first grabbed. | -| `.touchUpInside` | Called when a handle is let go. | +| `.touchDown`(no) | Called when a handle is first grabbed. | +| `.touchUpInside`(no) | Called when a handle is let go. | | `.valueChanged` | Called whenever the color has changed. | -| `.touchDragInside` | Called when a handle has moved via a drag action. | -| `.editingDidEnd` | Called when either a handle is let go or slider is let go. | +| `.touchDragInside`(no) | Called when a handle has moved via a drag action. | +| `.touchUpInside` | Called when a handle is released. | _ChromaBrightnessSlider_ | Event | Description | | :-----------------:|:-------------| +| `.touchDown` | Called when a the slider is grabbed. | | `.valueChanged` | Called whenever the slider is moved and the value has changed. | -| `.editingDidEnd` | Called when the slider handle is released. | +| `.touchUpInside` | Called when the slider handle is released. | ##### Example ```Swift diff --git a/Source/ChromaBrightnessSlider.swift b/Source/ChromaBrightnessSlider.swift index 1e109b5..a981b5f 100644 --- a/Source/ChromaBrightnessSlider.swift +++ b/Source/ChromaBrightnessSlider.swift @@ -29,15 +29,15 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable { public let handle = SliderHandleView() public var borderWidth: CGFloat = 4.0 { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } public var borderColor: UIColor = .white { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } public var showsShadow: Bool = true { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } //MARK: - Initialization @@ -79,7 +79,11 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable { public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { let location = touch.location(in: self) - return interactableBounds.contains(location) + let shouldBeginTracking = interactableBounds.contains(location) + if shouldBeginTracking { + sendActions(for: .touchDown) + } + return shouldBeginTracking } public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { @@ -87,13 +91,13 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable { let clampedPositionX: CGFloat = max(0, min(location.x, confiningTrackFrame.width)) let value = clampedPositionX / confiningTrackFrame.width - updateControl(to: value) + currentValue = value sendActions(for: .valueChanged) return true } public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { - sendActions(for: .editingDidEnd) + sendActions(for: .touchUpInside) } public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { @@ -103,47 +107,6 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable { return super.point(inside: point, with: event) } - // MARK: - Private - internal let sliderTrackView = SliderTrackView() - - /// The amount of padding caused by visual stylings - internal var horizontalPadding: CGFloat { - return sliderTrackView.layer.cornerRadius / 2.0 - } - - internal var confiningTrackFrame: CGRect { - return sliderTrackView.frame.insetBy(dx: horizontalPadding, dy: 0) - } - - internal var interactableBounds: CGRect { - let horizontalOffset = -(handle.bounds.width / 2) + horizontalPadding - return bounds.insetBy(dx: horizontalOffset, dy: 0) - } - - internal func commonInit() { - backgroundColor = .clear - setupSliderTrackView() - setupSliderHandleView() - updateTrackColor(to: trackColor) - } - - internal func setupSliderTrackView() { - sliderTrackView.isUserInteractionEnabled = false - sliderTrackView.translatesAutoresizingMaskIntoConstraints = false - addSubview(sliderTrackView) - NSLayoutConstraint.activate([ - sliderTrackView.leadingAnchor.constraint(equalTo: leadingAnchor), - sliderTrackView.trailingAnchor.constraint(equalTo: trailingAnchor), - sliderTrackView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75), - sliderTrackView.centerYAnchor.constraint(equalTo: centerYAnchor), - ]) - } - - internal func setupSliderHandleView() { - handle.isUserInteractionEnabled = false - addSubview(handle) - } - internal func updateShadowIfNeeded() { let views = [handle, sliderTrackView] @@ -155,7 +118,48 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable { } } - internal func updateControl(to value: CGFloat) { + // MARK: - Private + private let sliderTrackView = SliderTrackView() + + /// The amount of padding caused by visual stylings + private var horizontalPadding: CGFloat { + return sliderTrackView.layer.cornerRadius / 2.0 + } + + private var confiningTrackFrame: CGRect { + return sliderTrackView.frame.insetBy(dx: horizontalPadding, dy: 0) + } + + private var interactableBounds: CGRect { + let horizontalOffset = -(handle.bounds.width / 2) + horizontalPadding + return bounds.insetBy(dx: horizontalOffset, dy: 0) + } + + private func commonInit() { + backgroundColor = .clear + setupSliderTrackView() + setupSliderHandleView() + updateTrackColor(to: trackColor) + } + + private func setupSliderTrackView() { + sliderTrackView.isUserInteractionEnabled = false + sliderTrackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(sliderTrackView) + NSLayoutConstraint.activate([ + sliderTrackView.leadingAnchor.constraint(equalTo: leadingAnchor), + sliderTrackView.trailingAnchor.constraint(equalTo: trailingAnchor), + sliderTrackView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75), + sliderTrackView.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) + } + + private func setupSliderHandleView() { + handle.isUserInteractionEnabled = false + addSubview(handle) + } + + private func updateControl(to value: CGFloat) { let brightness = 1 - max(0, min(1, value)) var hue: CGFloat = 0 var saturation: CGFloat = 0 @@ -171,7 +175,7 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable { moveHandle(to: value) } - internal func updateTrackColor(to color: UIColor) { + private func updateTrackColor(to color: UIColor) { var hue: CGFloat = 0 var saturation: CGFloat = 0 var brightness: CGFloat = 0 @@ -183,14 +187,14 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable { currentValue = 1 - brightness } - internal func updateTrackViewGradient(for color: UIColor) { + private func updateTrackViewGradient(for color: UIColor) { CATransaction.begin() CATransaction.setDisableActions(true) sliderTrackView.gradientValues = (color, .black) CATransaction.commit() } - internal func moveHandle(to value: CGFloat) { + private func moveHandle(to value: CGFloat) { let clampedValue = max(0, min(1, value)) let xPos = (clampedValue * confiningTrackFrame.width) + horizontalPadding let size = CGSize(width: bounds.height * 1.15, height: bounds.height) diff --git a/Source/ChromaColorPicker.swift b/Source/ChromaColorPicker.swift index 0fb44b6..eb7f8fc 100644 --- a/Source/ChromaColorPicker.swift +++ b/Source/ChromaColorPicker.swift @@ -21,15 +21,15 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable { public weak var delegate: ChromaColorPickerDelegate? @IBInspectable public var borderWidth: CGFloat = 6.0 { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } @IBInspectable public var borderColor: UIColor = .white { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } @IBInspectable public var showsShadow: Bool = true { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } /// A brightness slider attached via the `connect(_:)` method. @@ -41,7 +41,7 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable { /// The size handles should be displayed at. public var handleSize: CGSize = defaultHandleSize { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } /// An extension to handles' hitboxes in the +Y direction. @@ -156,7 +156,7 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable { if let handle = currentHandle { animateHandleScale(handle, shouldGrow: false) } - sendActions(for: .editingDidEnd) + sendActions(for: .touchUpInside) } public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/Source/ColorWheelView.swift b/Source/ColorWheelView.swift index 3597428..d48d426 100644 --- a/Source/ColorWheelView.swift +++ b/Source/ColorWheelView.swift @@ -60,6 +60,7 @@ public class ColorWheelView: UIView { Returns the color on the wheel on a given point relative to the view. nil is returned if the point does not exist within the bounds of the color wheel. */ + // TODO: replace this function with a mathmatically based one in ChromaColorPicker public func pixelColor(at point: CGPoint) -> UIColor? { guard pointIsInColorWheel(point) else { return nil } diff --git a/Source/Extensions/UIView+Utils.swift b/Source/Extensions/UIView+Utils.swift new file mode 100644 index 0000000..07444ca --- /dev/null +++ b/Source/Extensions/UIView+Utils.swift @@ -0,0 +1,18 @@ +// +// UIView+Utils.swift +// ChromaColorPicker +// +// Created by Jon Cardasis on 9/8/19. +// Copyright © 2019 Jonathan Cardasis. All rights reserved. +// + +import UIKit + +internal extension UIView { + + /// Forces the view to layout synchronously and immediately + func layoutNow() { + setNeedsLayout() + layoutIfNeeded() + } +} diff --git a/Source/Helpers/ChromaColorHandle.swift b/Source/Supporting Views/ChromaColorHandle.swift similarity index 96% rename from Source/Helpers/ChromaColorHandle.swift rename to Source/Supporting Views/ChromaColorHandle.swift index 33bd9fd..f56bb95 100644 --- a/Source/Helpers/ChromaColorHandle.swift +++ b/Source/Supporting Views/ChromaColorHandle.swift @@ -12,7 +12,7 @@ public class ChromaColorHandle: UIView, ChromaControlStylable { /// Current selected color of the handle. public var color: UIColor = .black { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } /// An image to display in the handle. Updates `accessoryView` to be a UIImageView. @@ -36,19 +36,19 @@ public class ChromaColorHandle: UIView, ChromaControlStylable { /// The amount an accessory view's frame should be inset by. public var accessoryViewEdgeInsets: UIEdgeInsets = .zero { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } public var borderWidth: CGFloat = 3.0 { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } public var borderColor: UIColor = .white { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } public var showsShadow: Bool = true { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } // MARK: - Initialization diff --git a/Source/Helpers/SliderHandleView.swift b/Source/Supporting Views/SliderHandleView.swift similarity index 80% rename from Source/Helpers/SliderHandleView.swift rename to Source/Supporting Views/SliderHandleView.swift index 6505e3d..ce8cbcd 100644 --- a/Source/Helpers/SliderHandleView.swift +++ b/Source/Supporting Views/SliderHandleView.swift @@ -15,11 +15,11 @@ public class SliderHandleView: UIView { } public var borderWidth: CGFloat = 3.0 { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } public var borderColor: UIColor = .white { - didSet { layoutIfNeeded() } + didSet { layoutNow() } } override public init(frame: CGRect) { @@ -41,24 +41,18 @@ public class SliderHandleView: UIView { } // MARK: - Private - internal let handleLayer = CAShapeLayer() + private let handleLayer = CAShapeLayer() - internal struct CornerPoint { - let center: CGPoint - let startAngle: CGFloat - let endAngle: CGFloat - } - - internal func commonInit() { + private func commonInit() { layer.addSublayer(handleLayer) updateHandleColor(to: handleColor) } - internal func updateHandleColor(to color: UIColor) { + private func updateHandleColor(to color: UIColor) { handleLayer.fillColor = color.cgColor } - internal func makeRoundedTrianglePath(width: CGFloat, height: CGFloat, radius: CGFloat) -> CGPath { + private func makeRoundedTrianglePath(width: CGFloat, height: CGFloat, radius: CGFloat) -> CGPath { let point1 = CGPoint(x: -width / 2, y: height / 2) let point2 = CGPoint(x: 0, y: -height / 2) let point3 = CGPoint(x: width / 2, y: height / 2) diff --git a/Source/Helpers/SliderTrackView.swift b/Source/Supporting Views/SliderTrackView.swift similarity index 100% rename from Source/Helpers/SliderTrackView.swift rename to Source/Supporting Views/SliderTrackView.swift diff --git a/Tests/ChromaBrightnessSliderTests.swift b/Tests/ChromaBrightnessSliderTests.swift new file mode 100644 index 0000000..aca0847 --- /dev/null +++ b/Tests/ChromaBrightnessSliderTests.swift @@ -0,0 +1,251 @@ +// +// ChromaBrightnessSliderTests.swift +// ChromaColorPickerTests +// +// Created by Jon Cardasis on 9/8/19. +// Copyright © 2019 Jonathan Cardasis. All rights reserved. +// + +import XCTest +@testable import ChromaColorPicker + +class ChromaBrightnessSliderTests: XCTestCase { + + // MARK: - Properties + + func testCurrentColorShouldReturnBlueWhenTrackColorIsBlueAndCurrentValueIsZero() { + // Given + let subject = ChromaBrightnessSlider() + let expectedColor: UIColor = .blue + + // When + subject.trackColor = .blue + subject.currentValue = 0.0 + + // Then + XCTAssertEqual(subject.currentColor, expectedColor) + } + + func testCurrentColorShouldReturnBlackWhenTrackColorIsAnyAndCurrentValueIsOne() { + // Given + let subject = ChromaBrightnessSlider() + let expectedColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1) + + // When + subject.trackColor = .blue + subject.currentValue = 1.0 + + // Then + XCTAssertEqual(subject.currentColor, expectedColor) + } + + func testBorderWidthShouldUpdateSliderTrack() { + // Given + let subject = ChromaBrightnessSlider() + let sliderTrack = subject.test_sliderTrackView + let expectedBorderWidth: CGFloat = 11 + + // When + subject.borderWidth = expectedBorderWidth + + // Then + XCTAssertEqual(sliderTrack.layer.borderWidth, expectedBorderWidth) + } + + func testBorderColorShouldUpdateSliderTrack() { + // Given + let subject = ChromaBrightnessSlider() + let sliderTrack = subject.test_sliderTrackView + let expectedBorderColor: UIColor = .green + + // When + subject.borderColor = expectedBorderColor + + // Then + XCTAssertEqual(sliderTrack.layer.borderColor!, expectedBorderColor.cgColor) + } + + // MARK: - Layout + + func testShadowsAreRenderedOnBothHandleAndTrackView() { + // Given + let subject = ChromaBrightnessSlider() + let sliderTrack = subject.test_sliderTrackView + + // When + subject.showsShadow = true + + // Then + XCTAssertGreaterThan(sliderTrack.layer.shadowOpacity, 0) + XCTAssertGreaterThan(subject.handle.layer.shadowOpacity, 0) + } + + func testShadowsAreRemovedFromHandleAndTrackView() { + // Given + let subject = ChromaBrightnessSlider() + let sliderTrack = subject.test_sliderTrackView + + // When + subject.showsShadow = false + + // Then + XCTAssertEqual(sliderTrack.layer.shadowOpacity, 0) + XCTAssertEqual(subject.handle.layer.shadowOpacity, 0) + } + + // MARK: - Convenience Functions + + func testConnectCallsConnectFunctionOfColorPicker() { + // Given + let subject = ChromaBrightnessSlider() + let colorPicker = FakeColorPicker() + + // When + subject.connect(to: colorPicker) + + // Then + XCTAssertTrue(colorPicker.didCallConnect) + } + + func testValueShouldReturn1WhenBrightnessIs0() { + // Given + let subject = ChromaBrightnessSlider() + + // When + let value = subject.value(brightness: 0) + + // Then + XCTAssertEqual(value, 1, accuracy: 0.0001) + } + + func testValueShouldReturn0WhenBrightnessIs1() { + // Given + let subject = ChromaBrightnessSlider() + + // When + let value = subject.value(brightness: 1) + + // Then + XCTAssertEqual(value, 0, accuracy: 0.0001) + } + + func testValueShouldReturn0WhenBrightnessIsLargerThan1() { + // Given + let subject = ChromaBrightnessSlider() + + // When + let value = subject.value(brightness: 100) + + // Then + XCTAssertEqual(value, 0, accuracy: 0.0001) + } + + func testValueShouldReturn1WhenBrightnessIsLessThan0() { + // Given + let subject = ChromaBrightnessSlider() + + // When + let value = subject.value(brightness: -100) + + // Then + XCTAssertEqual(value, 1, accuracy: 0.0001) + } + + func testValueShouldReturn0_25WhenBrightnessIs0_75() { + // Given + let subject = ChromaBrightnessSlider() + + // When + let value = subject.value(brightness: 0.75) + + // Then + XCTAssertEqual(value, 0.25, accuracy: 0.0001) + } + + // MARK: - Control + + func testBeginTrackingSendsTouchDownActionWhenTouchIsInBounds() { + // Given + let subject = ChromaBrightnessSlider(frame: CGRect(x: 0, y: 0, width: 300, height: 30)) + subject.layoutIfNeeded() + let fakeTouch = FakeUITouch(locationInParent: CGPoint(x: subject.bounds.midX, + y: subject.bounds.midY)) + let eventReceiver = FakeEventReceiver(listensFor: .touchDown) + subject.addTarget(eventReceiver, action: #selector(FakeEventReceiver.catchEvent(_:)), for: .touchDown) + + var eventDidTrigger = false + eventReceiver.eventCaught = { + eventDidTrigger = true + } + + // When + let shouldTrack = subject.beginTracking(fakeTouch, with: nil) + + // Then + XCTAssertTrue(eventDidTrigger) + XCTAssertTrue(shouldTrack) + } + + func testBeginTrackingDoesNotSendTouchDownActionWhenTouchIsOutOfBounds() { + // Given + let subject = ChromaBrightnessSlider(frame: CGRect(x: 0, y: 0, width: 300, height: 30)) + subject.layoutIfNeeded() + let fakeTouch = FakeUITouch(locationInParent: CGPoint(x: -100, y: subject.bounds.midY)) + let eventReceiver = FakeEventReceiver(listensFor: .touchDown) + subject.addTarget(eventReceiver, action: #selector(FakeEventReceiver.catchEvent(_:)), for: .touchDown) + + var eventDidTrigger = false + eventReceiver.eventCaught = { + eventDidTrigger = true + } + + // When + let shouldTrack = subject.beginTracking(fakeTouch, with: nil) + + // Then + XCTAssertFalse(eventDidTrigger) + XCTAssertFalse(shouldTrack) + } + + func testContinueTrackingSendsValueChangedActionAndHasClampedValue() { + // Given + let subject = ChromaBrightnessSlider(frame: CGRect(x: 0, y: 0, width: 300, height: 30)) + subject.layoutIfNeeded() + let fakeTouch = FakeUITouch(locationInParent: CGPoint(x: subject.bounds.width + 100, y: subject.bounds.midY)) + let eventReceiver = FakeEventReceiver(listensFor: .valueChanged) + subject.addTarget(eventReceiver, action: #selector(FakeEventReceiver.catchEvent(_:)), for: .valueChanged) + + var eventDidTrigger = false + var value: CGFloat? + eventReceiver.eventCaught = { + eventDidTrigger = true + value = subject.currentValue + } + + // When + let _ = subject.continueTracking(fakeTouch, with: nil) + + // Then + XCTAssertTrue(eventDidTrigger) + XCTAssertEqual(value, 1.0, "ChromaBrightnessSlider did not clamp value when touch is out of bounds") + } + + func testEndTrackingSendsTouchUpInsideAction() { + + } +} + +fileprivate extension ChromaBrightnessSlider { + var test_sliderTrackView: SliderTrackView { + return subviews.first(where: { $0 is SliderTrackView }) as! SliderTrackView + } +} + + +fileprivate class FakeColorPicker: ChromaColorPicker { + var didCallConnect: Bool = false + + override func connect(_ slider: ChromaBrightnessSlider) { + didCallConnect = true + } +} diff --git a/Tests/ChromaColorPickerTests.swift b/Tests/ChromaColorPickerTests.swift index c0f1b04..f94f96e 100644 --- a/Tests/ChromaColorPickerTests.swift +++ b/Tests/ChromaColorPickerTests.swift @@ -153,52 +153,42 @@ class ChromaColorPickerTests: XCTestCase { setCurrentHandle(to: makeFakeHandle()) let fakeTouch = FakeUITouch(locationInParent: CGPoint(x: subject.colorWheelView.bounds.midX, y: subject.colorWheelView.bounds.midY)) - let eventReceiver = FakeEventReceiver() + let eventReceiver = FakeEventReceiver(listensFor: .valueChanged) subject.addTarget(eventReceiver, action: #selector(FakeEventReceiver.catchEvent), for: .valueChanged) - let expectation = XCTestExpectation(description: "event fired") - - eventReceiver.eventCaught = { event in - if event == .valueChanged { - expectation.fulfill() - } else { - XCTFail() - } + var eventDidTrigger = false + eventReceiver.eventCaught = { + eventDidTrigger = true } // When let _ = subject.continueTracking(fakeTouch, with: nil) // Then - wait(for: [expectation], timeout: 0.05) + XCTAssertTrue(eventDidTrigger) } - func testEndTrackingSendsEditingDidEndAction() { + func testEndTrackingSendsTouchUpInsideAction() { subject.colorWheelView.layoutIfNeeded() // Given setCurrentHandle(to: makeFakeHandle()) let fakeTouch = FakeUITouch(locationInParent: CGPoint(x: subject.colorWheelView.bounds.midX, y: subject.colorWheelView.bounds.midY)) - let eventReceiver = FakeEventReceiver() - subject.addTarget(eventReceiver, action: #selector(FakeEventReceiver.catchEvent), for: .editingDidEnd) + let eventReceiver = FakeEventReceiver(listensFor: .touchUpInside) + subject.addTarget(eventReceiver, action: #selector(FakeEventReceiver.catchEvent), for: .touchUpInside) - let expectation = XCTestExpectation(description: "event fired") - - eventReceiver.eventCaught = { event in - if event == .valueChanged { - expectation.fulfill() - } else { - XCTFail() - } + var eventDidTrigger = false + eventReceiver.eventCaught = { + eventDidTrigger = true } // When let _ = subject.endTracking(fakeTouch, with: nil) // Then - wait(for: [expectation], timeout: 0.05) + XCTAssertTrue(eventDidTrigger) } } @@ -216,25 +206,3 @@ extension ChromaColorPickerTests { let _ = subject.beginTracking(fakeTouch, with: nil) } } - - -private class FakeUITouch: UITouch { - - let locationInParent: CGPoint - - init(locationInParent: CGPoint) { - self.locationInParent = locationInParent - } - - override func location(in view: UIView?) -> CGPoint { - return locationInParent - } -} - -private class FakeEventReceiver: NSObject { - var eventCaught: ((UIControl.Event) -> ())? - - @objc func catchEvent() { - eventCaught?(.valueChanged) - } -} diff --git a/Tests/Helpers/SliderTrackViewTests.swift b/Tests/Helpers/SliderTrackViewTests.swift deleted file mode 100644 index 30b5b28..0000000 --- a/Tests/Helpers/SliderTrackViewTests.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// SliderTrackViewTests.swift -// ChromaColorPickerTests -// -// Created by Jon Cardasis on 5/13/19. -// Copyright © 2019 Jonathan Cardasis. All rights reserved. -// - -import Foundation diff --git a/Tests/Helpers/ChromaColorHandleTests.swift b/Tests/Supporting Views/ChromaColorHandleTests.swift similarity index 100% rename from Tests/Helpers/ChromaColorHandleTests.swift rename to Tests/Supporting Views/ChromaColorHandleTests.swift diff --git a/Tests/Helpers/SliderHandleViewTests.swift b/Tests/Supporting Views/SliderHandleViewTests.swift similarity index 99% rename from Tests/Helpers/SliderHandleViewTests.swift rename to Tests/Supporting Views/SliderHandleViewTests.swift index 9371979..b06d26a 100644 --- a/Tests/Helpers/SliderHandleViewTests.swift +++ b/Tests/Supporting Views/SliderHandleViewTests.swift @@ -30,7 +30,6 @@ class SliderHandleViewTests: XCTestCase { let layer = handleLayer(for: subject) let expectedColor: UIColor = .purple - // When subject.borderColor = expectedColor diff --git a/Tests/TestHelpers/FakeTouch.swift b/Tests/TestHelpers/FakeTouch.swift new file mode 100644 index 0000000..bebce1f --- /dev/null +++ b/Tests/TestHelpers/FakeTouch.swift @@ -0,0 +1,37 @@ +// +// FakeTouch.swift +// ChromaColorPickerTests +// +// Created by Jon Cardasis on 9/8/19. +// Copyright © 2019 Jonathan Cardasis. All rights reserved. +// + +import UIKit + +class FakeUITouch: UITouch { + + let locationInParent: CGPoint + + init(locationInParent: CGPoint) { + self.locationInParent = locationInParent + } + + override func location(in view: UIView?) -> CGPoint { + return locationInParent + } +} + +class FakeEventReceiver: NSObject { + var eventCaught: (() -> ())? + let event: UIControl.Event + + init(listensFor event: UIControl.Event) { + self.event = event + } + + @objc func catchEvent(_ sender: UIControl) { + if sender.allControlEvents.contains(event) { + eventCaught?() + } + } +}