From 8bf9b34f9b48fe053c892dbd9e9bdae85edf3e46 Mon Sep 17 00:00:00 2001 From: Jon Cardasis Date: Fri, 8 Nov 2019 16:17:06 -0500 Subject: [PATCH] Resolved issue with handleSize. Added additional tests. Example homeHandle now changes tint based on lightness of selected color --- ChromaColorPicker.xcodeproj/project.pbxproj | 8 +++ Example/ViewController.swift | 31 ++++++++++- Source/ChromaColorPicker.swift | 5 +- Source/Extensions/UIColor+Utils.swift | 28 ++++++++++ Tests/ChromaColorPickerTests.swift | 43 +++++++++++++++ Tests/Extensions/UIColor+UtilsTests.swift | 61 +++++++++++++++++++++ 6 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 Source/Extensions/UIColor+Utils.swift create mode 100644 Tests/Extensions/UIColor+UtilsTests.swift diff --git a/ChromaColorPicker.xcodeproj/project.pbxproj b/ChromaColorPicker.xcodeproj/project.pbxproj index 9705c06..4689704 100644 --- a/ChromaColorPicker.xcodeproj/project.pbxproj +++ b/ChromaColorPicker.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 35C376D61D5CF5300069D7A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D41D5CF5300069D7A1 /* Main.storyboard */; }; 35C376D81D5CF5300069D7A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D71D5CF5300069D7A1 /* Assets.xcassets */; }; 35C376DB1D5CF5300069D7A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D91D5CF5300069D7A1 /* LaunchScreen.storyboard */; }; + C83A935D23760C1600564E01 /* UIColor+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83A935C23760C1600564E01 /* UIColor+Utils.swift */; }; + C83A936023760C5900564E01 /* UIColor+UtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83A935E23760C3800564E01 /* UIColor+UtilsTests.swift */; }; FC1BD8C02207D7B700817AF3 /* ChromaColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerTests.swift */; }; FC1BD8C22207D7B700817AF3 /* ChromaColorPicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3503B82F1F2689BC00750356 /* ChromaColorPicker.framework */; }; FC4387C422603AE900F739F1 /* ColorWheelViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42AB226038A400BE2FF9 /* ColorWheelViewTests.swift */; }; @@ -87,6 +89,8 @@ 35C376D71D5CF5300069D7A1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35C376DA1D5CF5300069D7A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 35C376DC1D5CF5300069D7A1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C83A935C23760C1600564E01 /* UIColor+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Utils.swift"; sourceTree = ""; }; + C83A935E23760C3800564E01 /* UIColor+UtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+UtilsTests.swift"; sourceTree = ""; }; FC1BD8BD2207D7B700817AF3 /* ChromaColorPickerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChromaColorPickerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorPickerTests.swift; sourceTree = ""; }; FC1BD8C12207D7B700817AF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -215,6 +219,7 @@ isa = PBXGroup; children = ( FC4387DC226972EB00F739F1 /* UIColor+Brightness.swift */, + C83A935C23760C1600564E01 /* UIColor+Utils.swift */, FCCA42A92260329900BE2FF9 /* UIView+DropShadow.swift */, FC89B7E32325C24F00D007AB /* UIView+Utils.swift */, ); @@ -225,6 +230,7 @@ isa = PBXGroup; children = ( FCE07BFD227B4A8100920217 /* UIColor+BrightnessTests.swift */, + C83A935E23760C3800564E01 /* UIColor+UtilsTests.swift */, FCE07C03227B4DB900920217 /* UIView+DropShadowTests.swift */, ); path = Extensions; @@ -405,6 +411,7 @@ FCCA42AA2260329900BE2FF9 /* UIView+DropShadow.swift in Sources */, FC4387DE226974FD00F739F1 /* UIColor+Brightness.swift in Sources */, FC4387C62262556600F739F1 /* ChromaBrightnessSlider.swift in Sources */, + C83A935D23760C1600564E01 /* UIColor+Utils.swift in Sources */, FCCA42A7226023F000BE2FF9 /* ChromaColorHandle.swift in Sources */, FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */, FC4387C922625C7000F739F1 /* SliderTrackView.swift in Sources */, @@ -426,6 +433,7 @@ buildActionMask = 2147483647; files = ( FCE07C06227B525000920217 /* ChromaControlStylableTests.swift in Sources */, + C83A936023760C5900564E01 /* UIColor+UtilsTests.swift in Sources */, FC89B7E02325B07F00D007AB /* ChromaBrightnessSliderTests.swift in Sources */, FC89B7E22325B8B900D007AB /* FakeTouch.swift in Sources */, FCE07C0C228A0F7500920217 /* ChromaColorHandleTests.swift in Sources */, diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 3d5bc76..1677f62 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -22,6 +22,13 @@ class ViewController: UIViewController { setupColorPickerHandles() } + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + // MARK: - Private + private var homeHandle: ChromaColorHandle! // reference to home handle + private func setupColorPicker() { colorPicker.delegate = self colorPicker.translatesAutoresizingMaskIntoConstraints = false @@ -57,14 +64,24 @@ class ViewController: UIViewController { } private func setupColorPickerHandles() { + // (Optional) Assign a custom handle size - all handles appear as the same size + // colorPicker.handleSize = CGSize(width: 48, height: 60) + + // 1. Add handle and then customize addHomeHandle() + // 2. Add a handle via a color let peachColor = UIColor(red: 1, green: 203 / 255, blue: 164 / 255, alpha: 1) colorPicker.addHandle(at: peachColor) + + // 3. Create a custom handle and add to picker + let customHandle = ChromaColorHandle() + customHandle.color = UIColor.purple + colorPicker.addHandle(customHandle) } private func addHomeHandle() { - let homeHandle = colorPicker.addHandle(at: .blue) + homeHandle = colorPicker.addHandle(at: .blue) // Setup custom handle view with insets let customImageView = UIImageView(image: #imageLiteral(resourceName: "home").withRenderingMode(.alwaysTemplate)) @@ -78,8 +95,18 @@ class ViewController: UIViewController { extension ViewController: ChromaColorPickerDelegate { func colorPickerHandleDidChange(_ colorPicker: ChromaColorPicker, handle: ChromaColorHandle, to color: UIColor) { colorDisplayView.backgroundColor = color + + // Here I can detect when the color is too bright to show a white icon + // on the handle and change its tintColor. + if handle === homeHandle, let imageView = homeHandle.accessoryView as? UIImageView { + let colorIsBright = color.isLight + print(color.lightness) + + UIView.animate(withDuration: 0.2, animations: { + imageView.tintColor = colorIsBright ? .black : .white + }, completion: nil) + } } - } diff --git a/Source/ChromaColorPicker.swift b/Source/ChromaColorPicker.swift index eb7f8fc..0d7a035 100644 --- a/Source/ChromaColorPicker.swift +++ b/Source/ChromaColorPicker.swift @@ -1,5 +1,5 @@ // -// ChromaColorPicker2.swift +// ChromaColorPicker.swift // ChromaColorPicker // // Created by Jon Cardasis on 3/10/19. @@ -7,7 +7,6 @@ // import UIKit -import Accelerate public protocol ChromaColorPickerDelegate: class { /// When a handle's value has changed. @@ -41,7 +40,7 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable { /// The size handles should be displayed at. public var handleSize: CGSize = defaultHandleSize { - didSet { layoutNow() } + didSet { setNeedsLayout() } } /// An extension to handles' hitboxes in the +Y direction. diff --git a/Source/Extensions/UIColor+Utils.swift b/Source/Extensions/UIColor+Utils.swift new file mode 100644 index 0000000..9fcfcb9 --- /dev/null +++ b/Source/Extensions/UIColor+Utils.swift @@ -0,0 +1,28 @@ +// +// UIColor+Utils.swift +// ChromaColorPicker +// +// Created by Jonathan Cardasis on 11/8/19. +// Copyright © 2019 Jonathan Cardasis. All rights reserved. +// + +import UIKit + +public extension UIColor { + + /// The value of lightness a color has. Value between [0.0, 1.0] + /// Based on YIQ color space for constrast (https://www.w3.org/WAI/ER/WD-AERT/#color-contrast) + var lightness: CGFloat { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + getRed(&red, green: &green, blue: &blue, alpha: nil) + + return ((red * 299) + (green * 587) + (blue * 114)) / 1000 + } + + /// Whether or not the color is considered 'light' in terms of contrast. + var isLight: Bool { + return lightness >= 0.5 + } +} diff --git a/Tests/ChromaColorPickerTests.swift b/Tests/ChromaColorPickerTests.swift index f94f96e..7ad2bc2 100644 --- a/Tests/ChromaColorPickerTests.swift +++ b/Tests/ChromaColorPickerTests.swift @@ -190,6 +190,49 @@ class ChromaColorPickerTests: XCTestCase { // Then XCTAssertTrue(eventDidTrigger) } + + func testHitTestShouldReturnSelfWhenTouchIsWithinSelf() { + subject.colorWheelView.layoutIfNeeded() + + // Given + let touchLocation = subject.center + + // When + let hitView = subject.hitTest(touchLocation, with: UIEvent()) + + // Then + XCTAssertEqual(hitView, subject) + } + + func testHitTestShouldReturnSelfWhenTouchIsWithinSelfPlusHandleRadius() { + subject.colorWheelView.layoutIfNeeded() + + // Given + let touchLocation = CGPoint(x: -subject.handleSize.width / 2.0, y: subject.center.y) + + // When + let hitView = subject.hitTest(touchLocation, with: UIEvent()) + + // Then + XCTAssertEqual(hitView, subject) + } + + func testHitTestShouldReturnNilWhenTouchIsOutsideHitbox() { + subject.colorWheelView.layoutIfNeeded() + + // Given + let extendedSize: CGFloat = 12 + subject.handleSize = CGSize(width: extendedSize, height: extendedSize) + let fakeHandle = makeFakeHandle() + let touchLocation = CGPoint(x: -extendedSize - 1, y: subject.center.y) + setCurrentHandle(to: fakeHandle) + + // When + let hitView = subject.hitTest(touchLocation, with: UIEvent()) + + // Then + XCTAssertEqual(hitView, nil) + } } diff --git a/Tests/Extensions/UIColor+UtilsTests.swift b/Tests/Extensions/UIColor+UtilsTests.swift new file mode 100644 index 0000000..84896e9 --- /dev/null +++ b/Tests/Extensions/UIColor+UtilsTests.swift @@ -0,0 +1,61 @@ +// +// UIColor+UtilsTests.swift +// ChromaColorPicker +// +// Created by Jonathan Cardasis on 11/8/19. +// Copyright © 2019 Jonathan Cardasis. All rights reserved. +// + +import XCTest +@testable import ChromaColorPicker + +class UIColor_UtilsTests: XCTestCase { + + func testLightnessReturns1ForWhite() { + XCTAssertEqual(UIColor.white.lightness, 1.0, accuracy: 0.001) + } + + func testLightnessReturns0ForBlack() { + XCTAssertEqual(UIColor.black.lightness, 0.0, accuracy: 0.001) + } + + func testLightnessReturnsCorrectLightnessForYellow() { + XCTAssertEqual(UIColor.yellow.lightness, 0.886, accuracy: 0.001) + } + + func testLightnessReturnsCorrectLightnessForBlue() { + XCTAssertEqual(UIColor.blue.lightness, 0.114, accuracy: 0.001) + } + + func testBlueIsNotLight() { + XCTAssertFalse(UIColor.blue.isLight) + } + + func testRedIsNotLight() { + XCTAssertFalse(UIColor.red.isLight) + } + + func testPurpleIsNotLight() { + XCTAssertFalse(UIColor.purple.isLight) + } + + func tesBlackIsNotLight() { + XCTAssertFalse(UIColor.black.isLight) + } + + func testWhiteIsLight() { + XCTAssertTrue(UIColor.white.isLight) + } + + func testGreenIsLight() { + XCTAssertTrue(UIColor.green.isLight) + } + + func testOrangeIsLight() { + XCTAssertTrue(UIColor.orange.isLight) + } + + func testYellowIsLight() { + XCTAssertTrue(UIColor.yellow.isLight) + } +}