diff --git a/CHANGELOG.md b/CHANGELOG.md index 45aebc2..581c82d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ +## Version 1.2.0 +### Features +* [Hex color input field](https://github.com/chrs1885/SheetyColors/issues/15) + ## Version 1.1.0 ### Features * SwiftUI support -* Expose SheetyColors view] +* [Expose SheetyColors view](https://github.com/chrs1885/SheetyColors/issues/13) ## Version 1.0.2 ### Enhancements -*[Support Swift Package Manager](https://github.com/chrs1885/SheetyColors/issues/10) +* [Support Swift Package Manager](https://github.com/chrs1885/SheetyColors/issues/10) ## Version 1.0.1 ### Bugfix diff --git a/Documentation/Reference/README.md b/Documentation/Reference/README.md index 98be691..b8ed4aa 100644 --- a/Documentation/Reference/README.md +++ b/Documentation/Reference/README.md @@ -1,21 +1,22 @@ -# Reference Documentation -This Reference Documentation has been generated with -[SourceDocs](https://github.com/eneko/SourceDocs). - ## Protocols - [SheetyColorProtocol](protocols/SheetyColorProtocol.md) - [SheetyColorsConfigProtocol](protocols/SheetyColorsConfigProtocol.md) +- [SheetyColorsDelegate](protocols/SheetyColorsDelegate.md) ## Structs - [SheetyColorsConfig](structs/SheetyColorsConfig.md) +- [SheetyColorsView](structs/SheetyColorsView.md) +- [SheetyColorsViewFactory](structs/SheetyColorsViewFactory.md) ## Classes +- [Coordinator](classes/Coordinator.md) - [GrayscaleColor](classes/GrayscaleColor.md) - [HSBAColor](classes/HSBAColor.md) - [RGBAColor](classes/RGBAColor.md) +- [SheetyColorsViewController](classes/SheetyColorsViewController.md) ## Enums @@ -28,3 +29,7 @@ This Reference Documentation has been generated with - [RGBAColor](extensions/RGBAColor.md) - [SheetyColorsController](extensions/SheetyColorsController.md) - [UIColor](extensions/UIColor.md) + +# Reference Documentation +This reference documentation was generated with +[SourceDocs](https://github.com/eneko/SourceDocs). \ No newline at end of file diff --git a/Documentation/Reference/classes/Coordinator.md b/Documentation/Reference/classes/Coordinator.md new file mode 100644 index 0000000..6ad1d52 --- /dev/null +++ b/Documentation/Reference/classes/Coordinator.md @@ -0,0 +1,14 @@ +**CLASS** + +# `Coordinator` + +```swift +public class Coordinator: NSObject, SheetyColorsDelegate +``` + +## Methods +### `didSelectColor(_:)` + +```swift +public func didSelectColor(_ color: UIColor) +``` diff --git a/Documentation/Reference/classes/RGBAColor.md b/Documentation/Reference/classes/RGBAColor.md index b2d3e8e..e29700e 100644 --- a/Documentation/Reference/classes/RGBAColor.md +++ b/Documentation/Reference/classes/RGBAColor.md @@ -18,10 +18,10 @@ public init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) > Creates a RGBAColor instance. > > - Parameter: -> - red: The red component. -> - green: The green component. -> - blue: The blue component. -> - alpha: The opacity component. +> - red: The red component. +> - green: The green component. +> - blue: The blue component. +> - alpha: The opacity component. ### `copy(with:)` diff --git a/Documentation/Reference/classes/SheetyColorsViewController.md b/Documentation/Reference/classes/SheetyColorsViewController.md new file mode 100644 index 0000000..f1c4a7d --- /dev/null +++ b/Documentation/Reference/classes/SheetyColorsViewController.md @@ -0,0 +1,16 @@ +**CLASS** + +# `SheetyColorsViewController` + +```swift +public class SheetyColorsViewController: UIViewController, SheetyColorsViewControllerProtocol +``` + +> The controller class managing a SheetyColors view. + +## Methods +### `viewDidLoad()` + +```swift +public override func viewDidLoad() +``` diff --git a/Documentation/Reference/extensions/SheetyColorsController.md b/Documentation/Reference/extensions/SheetyColorsController.md index 31ebc7d..574af54 100644 --- a/Documentation/Reference/extensions/SheetyColorsController.md +++ b/Documentation/Reference/extensions/SheetyColorsController.md @@ -21,4 +21,4 @@ convenience init(withConfig config: SheetyColorsConfigProtocol) > Creates a SheetyColorsController instance. > > - Parameter: -> - config: A config object containing options for specifying the look and feel of a SheetyColors view. +> - config: A config object containing options for specifying the look and feel of a SheetyColors view. diff --git a/Documentation/Reference/extensions/UIColor.md b/Documentation/Reference/extensions/UIColor.md index 68a937f..1fb750d 100644 --- a/Documentation/Reference/extensions/UIColor.md +++ b/Documentation/Reference/extensions/UIColor.md @@ -26,3 +26,10 @@ var rgbaColor: RGBAColor ``` > The RGBAColor representation of the UIColor instance. + +## Methods +### `init(hex:)` + +```swift +public convenience init?(hex: String) +``` diff --git a/Documentation/Reference/protocols/SheetyColorProtocol.md b/Documentation/Reference/protocols/SheetyColorProtocol.md index 131a18b..125ad6b 100644 --- a/Documentation/Reference/protocols/SheetyColorProtocol.md +++ b/Documentation/Reference/protocols/SheetyColorProtocol.md @@ -6,7 +6,7 @@ public protocol SheetyColorProtocol: Codable ``` -> A protocol defining a color object provided by the SheetyColors view.. +> A protocol defining a color object provided by the SheetyColors view. ## Properties ### `uiColor` diff --git a/Documentation/Reference/protocols/SheetyColorsDelegate.md b/Documentation/Reference/protocols/SheetyColorsDelegate.md new file mode 100644 index 0000000..fc81b7e --- /dev/null +++ b/Documentation/Reference/protocols/SheetyColorsDelegate.md @@ -0,0 +1,21 @@ +**PROTOCOL** + +# `SheetyColorsDelegate` + +```swift +public protocol SheetyColorsDelegate: AnyObject +``` + +> A protocol defining functions that are called when interacting with a color picker. + +## Methods +### `didSelectColor(_:)` + +```swift +func didSelectColor(_ color: UIColor) +``` + +> A delegate function that gets called once any slider value has been changed. +> +> - Parameter: +> - color: The updated color. diff --git a/Documentation/Reference/structs/SheetyColorsView.md b/Documentation/Reference/structs/SheetyColorsView.md new file mode 100644 index 0000000..f7ea0a8 --- /dev/null +++ b/Documentation/Reference/structs/SheetyColorsView.md @@ -0,0 +1,26 @@ +**STRUCT** + +# `SheetyColorsView` + +```swift +public struct SheetyColorsView: UIViewControllerRepresentable +``` + +## Methods +### `makeCoordinator()` + +```swift +public func makeCoordinator() -> Coordinator +``` + +### `makeUIViewController(context:)` + +```swift +public func makeUIViewController(context: UIViewControllerRepresentableContext) -> SheetyColorsViewController +``` + +### `updateUIViewController(_:context:)` + +```swift +public func updateUIViewController(_: SheetyColorsViewController, context _: UIViewControllerRepresentableContext) +``` diff --git a/Documentation/Reference/structs/SheetyColorsViewFactory.md b/Documentation/Reference/structs/SheetyColorsViewFactory.md new file mode 100644 index 0000000..728227b --- /dev/null +++ b/Documentation/Reference/structs/SheetyColorsViewFactory.md @@ -0,0 +1,24 @@ +**STRUCT** + +# `SheetyColorsViewFactory` + +```swift +public struct SheetyColorsViewFactory +``` + +> A factory for creating SheetyColors view controller + +## Methods +### `createView(withConfig:delegate:)` + +```swift +public static func createView(withConfig config: SheetyColorsConfigProtocol, delegate: SheetyColorsDelegate? = nil) -> SheetyColorsViewController +``` + +> Creates a SheetyColorsViewController instance based on a given configuration. +> +> - Parameter: +> - config: Defines all aspects of the view such as a color model type, alpha value support, texts, initial colors, or haptical feedback. +> - delegate: A delegate used for handling the color selection. A delegate needs to be provided in cases where you want to use the SheetyColorsViewController directly and not as part of a UIAlertViewController (e.g. SwiftUI). +> +> - Returns: A SheetyColorsViewController instance. diff --git a/Documentation/demo_customizable.png b/Documentation/demo_customizable.png index 53055dc..7ebb1b6 100644 Binary files a/Documentation/demo_customizable.png and b/Documentation/demo_customizable.png differ diff --git a/Documentation/demo_dark_mode.png b/Documentation/demo_dark_mode.png index f40dbbf..839e53a 100644 Binary files a/Documentation/demo_dark_mode.png and b/Documentation/demo_dark_mode.png differ diff --git a/Documentation/demo_minimum_configuration.png b/Documentation/demo_minimum_configuration.png index 2f66b47..6197c8f 100644 Binary files a/Documentation/demo_minimum_configuration.png and b/Documentation/demo_minimum_configuration.png differ diff --git a/Documentation/hex_input.gif b/Documentation/hex_input.gif new file mode 100644 index 0000000..b26db29 Binary files /dev/null and b/Documentation/hex_input.gif differ diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 692d9a4..f32ac4f 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -2,7 +2,7 @@ PODS: - Capable/Colors (1.1.4) - Nimble (8.0.7) - Quick (2.2.0) - - SheetyColors (1.1.0): + - SheetyColors (1.2.0): - Capable/Colors (~> 1.1) - SnapshotTesting (1.7.2) @@ -27,9 +27,9 @@ SPEC CHECKSUMS: Capable: 9f420f2a80a12144814ea2f0ba196516a0827ca1 Nimble: a73af6ecd4c9106f434f3d55fc54570be3739e0b Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e - SheetyColors: 8503d13ddcce9517906732ecda0193ba09ad5ee2 + SheetyColors: 8dc64a666415be8a81df3c5d44b9088d6f8bee2e SnapshotTesting: 8caa6661fea7c8019d5b46de77c16bab99c36c5c PODFILE CHECKSUM: 5095640969646fd5f5546f91228d960c22a55514 -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.1 diff --git a/Example/SheetyColors.xcodeproj/project.pbxproj b/Example/SheetyColors.xcodeproj/project.pbxproj index 01f1768..663ade7 100644 --- a/Example/SheetyColors.xcodeproj/project.pbxproj +++ b/Example/SheetyColors.xcodeproj/project.pbxproj @@ -8,8 +8,12 @@ /* Begin PBXBuildFile section */ 22AA8832D7526E840D7FA916 /* Pods_SheetyColors_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87B5CDD8C9040B059B8C1536 /* Pods_SheetyColors_Example.framework */; }; + 443A8A252447636500487BAD /* HapticFeedbackProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 443A8A242447636500487BAD /* HapticFeedbackProviderMock.swift */; }; 44B496B12233C0AB00CF8B38 /* ColorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B496B02233C0AB00CF8B38 /* ColorCell.swift */; }; 44D0F1F4243148E7001DF988 /* SheetyColorsDelegateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44D0F1F3243148E7001DF988 /* SheetyColorsDelegateMock.swift */; }; + 44D9423224445C81000B7F31 /* HexTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44D9423124445C81000B7F31 /* HexTextFieldTests.swift */; }; + 44D942352444C436000B7F31 /* UIColor+hexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44D942342444C436000B7F31 /* UIColor+hexTests.swift */; }; + 44D942382444DA08000B7F31 /* HexTextFieldDelegateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44D942372444DA08000B7F31 /* HexTextFieldDelegateMock.swift */; }; 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 607FACD81AFB9204008FA782 /* ColorsCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ColorsCollectionViewController.swift */; }; 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; @@ -53,8 +57,12 @@ 0E199A98F8761686DD180A4E /* SheetyColors.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SheetyColors.podspec; path = ../SheetyColors.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 1BB8CE96911C8C57D7DDDF3A /* Pods_SheetyColors_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SheetyColors_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3697BB2765259F63B9A5841A /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + 443A8A242447636500487BAD /* HapticFeedbackProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedbackProviderMock.swift; sourceTree = ""; }; 44B496B02233C0AB00CF8B38 /* ColorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCell.swift; sourceTree = ""; }; 44D0F1F3243148E7001DF988 /* SheetyColorsDelegateMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetyColorsDelegateMock.swift; sourceTree = ""; }; + 44D9423124445C81000B7F31 /* HexTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexTextFieldTests.swift; sourceTree = ""; }; + 44D942342444C436000B7F31 /* UIColor+hexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+hexTests.swift"; sourceTree = ""; }; + 44D942372444DA08000B7F31 /* HexTextFieldDelegateMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexTextFieldDelegateMock.swift; sourceTree = ""; }; 553AD8A7258AE141F7908ED0 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* SheetyColors_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SheetyColors_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -139,6 +147,23 @@ name = Views; sourceTree = ""; }; + 44D942332444C41D000B7F31 /* Extensions */ = { + isa = PBXGroup; + children = ( + 44D942342444C436000B7F31 /* UIColor+hexTests.swift */, + ); + name = Extensions; + sourceTree = ""; + }; + 44D942362444D9BC000B7F31 /* Mocks */ = { + isa = PBXGroup; + children = ( + 44D942372444DA08000B7F31 /* HexTextFieldDelegateMock.swift */, + 443A8A242447636500487BAD /* HapticFeedbackProviderMock.swift */, + ); + name = Mocks; + sourceTree = ""; + }; 607FACC71AFB9204008FA782 = { isa = PBXGroup; children = ( @@ -217,9 +242,11 @@ 83205EE22256812D00D62BB8 /* Views */ = { isa = PBXGroup; children = ( + 44D942362444D9BC000B7F31 /* Mocks */, 83205EE32256813800D62BB8 /* Extensions */, 831FCF01225E62F8003C48A4 /* SheetyColorsViewTests.swift */, 83DDE21B2262204F005F4CEF /* GradientSliderTests.swift */, + 44D9423124445C81000B7F31 /* HexTextFieldTests.swift */, ); name = Views; sourceTree = ""; @@ -365,6 +392,7 @@ 83DDE21F2264FD06005F4CEF /* Models */ = { isa = PBXGroup; children = ( + 44D942332444C41D000B7F31 /* Extensions */, 83DDE2202264FD1C005F4CEF /* SheetyColorsConfigTests.swift */, ); name = Models; @@ -628,6 +656,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 44D942382444DA08000B7F31 /* HexTextFieldDelegateMock.swift in Sources */, 832DCEFF226E4D7C00DAF3EF /* HSBViewModelTests.swift in Sources */, 832DCF03226F92E100DAF3EF /* UIColor+hsbaColorTests.swift in Sources */, 834A36E52249829300DECD37 /* SheetyColorsConfigMock.swift in Sources */, @@ -636,13 +665,16 @@ 83DDE21C2262204F005F4CEF /* GradientSliderTests.swift in Sources */, 834A36F2224E94FC00DECD37 /* SheetyColorsViewModelMock.swift in Sources */, 834A36E2224971F700DECD37 /* SheetyColorsViewFactoryTests.swift in Sources */, + 44D942352444C436000B7F31 /* UIColor+hexTests.swift in Sources */, 831FCF0022591DFE003C48A4 /* UIAlertController+customViewTests.swift in Sources */, 831FCF02225E62F8003C48A4 /* SheetyColorsViewTests.swift in Sources */, 836D683D22F8B4C5005452FB /* AppearenceProviderMock.swift in Sources */, 44D0F1F4243148E7001DF988 /* SheetyColorsDelegateMock.swift in Sources */, 83537FC52273A01000E91C39 /* GrayscaleViewModel.swift in Sources */, + 443A8A252447636500487BAD /* HapticFeedbackProviderMock.swift in Sources */, 83537FC222739E1400E91C39 /* UIColor+grayscaleColorTests.swift in Sources */, 834A36E8224E88F400DECD37 /* RGBViewModelTests.swift in Sources */, + 44D9423224445C81000B7F31 /* HexTextFieldTests.swift in Sources */, 834A36ED224E8C7400DECD37 /* SheetyColorsViewDelegateMock.swift in Sources */, 834A36F4224E98F900DECD37 /* SheetyColorsViewMock.swift in Sources */, ); diff --git a/Example/Tests/GradientSliderTests.swift b/Example/Tests/GradientSliderTests.swift index 4f6ff1a..20fba8c 100644 --- a/Example/Tests/GradientSliderTests.swift +++ b/Example/Tests/GradientSliderTests.swift @@ -27,8 +27,7 @@ class GradientSliderTests: QuickSpec { context("when initialized with default values") { it("renders the slider control") { let view = creatContainerView(withView: sut) - assertSnapshot(matching: view, as: .image(size: .init(width: testFrame.width, height: testFrame.height))) - assertSnapshot(matching: sut, as: .recursiveDescription(size: .init(width: testFrame.width, height: testFrame.height))) + assertSnapshot(matching: view, as: .image(size: .init(width: testFrame.width, height: testFrame.height)), named: "initial_default_state") } } @@ -45,14 +44,14 @@ class GradientSliderTests: QuickSpec { } it("renders the slider control") { let view = creatContainerView(withView: sut) - assertSnapshot(matching: view, as: .image(size: .init(width: testFrame.width, height: testFrame.height))) + assertSnapshot(matching: view, as: .image(size: .init(width: testFrame.width, height: testFrame.height)), named: "initial_custom_state") } } } } } -func creatContainerView(withView view: UIView) -> UIView { +private func creatContainerView(withView view: UIView) -> UIView { let container = UIView() container.addSubview(view) view.anchor(top: container.topAnchor, bottom: container.bottomAnchor, left: container.leftAnchor, right: container.rightAnchor) diff --git a/Example/Tests/GrayscaleViewModel.swift b/Example/Tests/GrayscaleViewModel.swift index 0742aef..497f831 100644 --- a/Example/Tests/GrayscaleViewModel.swift +++ b/Example/Tests/GrayscaleViewModel.swift @@ -18,24 +18,20 @@ class GrayscaleViewModelTests: QuickSpec { var delegateMock: SheetyColorsDelegateMock! context("after initialization") { - var testColorModel: GrayscaleColor! - var testIsAlphaEnabled: Bool! - var testHasTextOrMessage: Bool! + let testColorModel = GrayscaleColor(white: 120.0, alpha: 69.0) + let testConfig = SheetyColorsConfig(alphaEnabled: true, hapticFeedbackEnabled: true, initialColor: testColorModel.uiColor, title: "title", message: "message", type: .grayscale) beforeEach { viewDelegateMock = SheetyColorsViewDelegateMock() delegateMock = SheetyColorsDelegateMock() - testIsAlphaEnabled = true - testHasTextOrMessage = true - testColorModel = GrayscaleColor(white: 123.0, alpha: 69.0) - sut = GrayscaleViewModel(withColorModel: testColorModel, isAlphaEnabled: testIsAlphaEnabled, hasTextOrMessage: testHasTextOrMessage) + sut = GrayscaleViewModel(withConfig: testConfig) sut.viewDelegate = viewDelegateMock sut.delegate = delegateMock } context("when calling hasTextOrMessage property") { it("returns the correct state") { - expect(sut!.hasTextOrMessage).to(equal(testHasTextOrMessage)) + expect(sut!.hasTextOrMessage).to(beTrue()) } } diff --git a/Example/Tests/HSBViewModelTests.swift b/Example/Tests/HSBViewModelTests.swift index 98dde36..ae862e4 100644 --- a/Example/Tests/HSBViewModelTests.swift +++ b/Example/Tests/HSBViewModelTests.swift @@ -18,24 +18,20 @@ class HSBViewModelTests: QuickSpec { var delegateMock: SheetyColorsDelegateMock! context("after initialization") { - var testColorModel: HSBAColor! - var testIsAlphaEnabled: Bool! - var testHasTextOrMessage: Bool! + let testColorModel = HSBAColor(hue: 120.0, saturation: 94.0, brightness: 87.0, alpha: 69.0) + let testConfig = SheetyColorsConfig(alphaEnabled: true, hapticFeedbackEnabled: true, initialColor: testColorModel.uiColor, title: "title", message: "message", type: .hsb) beforeEach { viewDelegateMock = SheetyColorsViewDelegateMock() delegateMock = SheetyColorsDelegateMock() - testIsAlphaEnabled = true - testHasTextOrMessage = true - testColorModel = HSBAColor(hue: 123.0, saturation: 94.0, brightness: 87.0, alpha: 69.0) - sut = HSBViewModel(withColorModel: testColorModel, isAlphaEnabled: testIsAlphaEnabled, hasTextOrMessage: testHasTextOrMessage) + sut = HSBViewModel(withConfig: testConfig) sut.viewDelegate = viewDelegateMock sut.delegate = delegateMock } context("when calling hasTextOrMessage property") { it("returns the correct state") { - expect(sut!.hasTextOrMessage).to(equal(testHasTextOrMessage)) + expect(sut!.hasTextOrMessage).to(beTrue()) } } diff --git a/Example/Tests/HapticFeedbackProviderMock.swift b/Example/Tests/HapticFeedbackProviderMock.swift new file mode 100644 index 0000000..a9f2e26 --- /dev/null +++ b/Example/Tests/HapticFeedbackProviderMock.swift @@ -0,0 +1,33 @@ +// +// HapticFeedbackProviderMock.swift +// SheetyColors_Tests +// +// Created by Wendt, Christoph on 15.04.20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Foundation +@testable import SheetyColors + +class HapticFeedbackProviderMock: HapticFeedbackProviderProtocol { + var didCallGenerateInputFeedback = false + var didCallGenerateSelectionFeedback = false + var didCallGenerateErrorFeedback = false + var didCallResetSelectionFeedback = false + + func generateInputFeedback() { + didCallGenerateInputFeedback = true + } + + func generateSelectionFeedback() { + didCallGenerateSelectionFeedback = true + } + + func generateErrorFeedback() { + didCallGenerateErrorFeedback = true + } + + func resetSelectionFeedback() { + didCallResetSelectionFeedback = true + } +} diff --git a/Example/Tests/HexTextFieldDelegateMock.swift b/Example/Tests/HexTextFieldDelegateMock.swift new file mode 100644 index 0000000..f36c6cf --- /dev/null +++ b/Example/Tests/HexTextFieldDelegateMock.swift @@ -0,0 +1,20 @@ +// +// HexTextFieldDelegateMock.swift +// SheetyColors_Tests +// +// Created by Wendt, Christoph on 13.04.20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Foundation +@testable import SheetyColors + +class HexTextFieldDelegateMock: HexTextFieldDelegate { + var didEditHexValue = false + var hexValue: String? + + func hexTextField(_: HexTextField, didEditHexValue value: String) { + didEditHexValue = true + hexValue = value + } +} diff --git a/Example/Tests/HexTextFieldTests.swift b/Example/Tests/HexTextFieldTests.swift new file mode 100644 index 0000000..d8bcff3 --- /dev/null +++ b/Example/Tests/HexTextFieldTests.swift @@ -0,0 +1,184 @@ +// +// HexTextFieldTests.swift +// SheetyColors_Tests +// +// Created by Wendt, Christoph on 13.04.20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Nimble +import Quick +@testable import SheetyColors +import SnapshotTesting + +class HexTextFieldTests: QuickSpec { + override func spec() { + describe("The HexTextField") { + var window: UIWindow! + + beforeEach { + window = UIWindow(frame: UIScreen.main.bounds) + } + + afterEach { + window = nil + } + + context("when initialized") { + var sut: HexTextField! + var hapticFeedbackProviderMock: HapticFeedbackProviderMock! + var testFrame: CGRect! + + beforeEach { + testFrame = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 50.0) + hapticFeedbackProviderMock = HapticFeedbackProviderMock() + sut = HexTextField(hapticFeedbackProvider: hapticFeedbackProviderMock) + sut.text = "ABCDEF" + window.makeKeyAndVisible() + window.addSubview(sut) + sut.anchor(top: window.topAnchor, bottom: window.bottomAnchor, left: window.leftAnchor, right: window.rightAnchor) + } + + afterEach { + sut = nil + } + + it("manages 6 single textFields") { + assertSnapshot(matching: sut, as: .image(size: .init(width: testFrame.width, height: testFrame.height)), named: "initial_state") + } + + context("setting a new text color") { + beforeEach { + sut.textColor = .blue + } + + it("applies the text color on every single text field") { + for textField in sut.textFields { + expect(textField.textColor).to(equal(UIColor.blue)) + } + } + } + + context("setting a new text") { + let testText = ["a", "b", "c", "d", "e", "f"] + beforeEach { + sut.text = testText.joined() + } + + it("applies the text color on every single text field") { + for index in 0 ..< sut.textFields.count { + let currentTextField = sut.textFields[index] + expect(currentTextField.text).to(equal(testText[index])) + } + } + } + + context("after a new valid hex code was provided by the user") { + var delegateMock: HexTextFieldDelegateMock! + var testHex: String! + + beforeEach { + testHex = "FF0000" + delegateMock = HexTextFieldDelegateMock() + sut.delegate = delegateMock + sut.text = testHex + sut.textFieldDidEndEditing(UITextField()) + } + + it("informs its delegate") { + expect(delegateMock.didEditHexValue).to(beTrue()) + expect(delegateMock.hexValue).to(equal(testHex)) + } + } + + context("after the hex text field was selected") { + var textField: UITextField! + var nextTextField: UITextField! + + beforeEach { + textField = sut.textFields[0] + nextTextField = sut.textFields[1] + + sut.buttonPressed() + } + + it("removes the text") { + assertSnapshot(matching: sut, as: .image(size: .init(width: testFrame.width, height: testFrame.height)), named: "text_field_selected") + } + + context("when the user types in a valid hex element") { + let testHexElement = "D" + + beforeEach { + _ = sut.textField(textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: testHexElement) + } + + it("sets the text field's text to the new value") { + expect(textField.text).to(equal(testHexElement)) + } + + it("sets the next text field as first responder") { + expect(textField.isFirstResponder).to(beTrue()) + } + + it("generates a haptic input feedback") { + expect(hapticFeedbackProviderMock.didCallGenerateInputFeedback).to(beTrue()) + } + + context("when the deletes the hex element again") { + let testHexElement = "" + + beforeEach { + textField = sut.textFields[1] + nextTextField = sut.textFields[0] + _ = sut.textField(textField, shouldChangeCharactersIn: NSRange(location: 0, length: 1), replacementString: testHexElement) + } + + it("deletes the text field's text to the new value") { + expect(textField.text).to(beEmpty()) + } + + it("sets the previous text field as first responder") { + expect(nextTextField.isFirstResponder).to(beTrue()) + } + + it("generates a haptic input feedback") { + expect(hapticFeedbackProviderMock.didCallGenerateInputFeedback).to(beTrue()) + } + } + } + + context("when the user types in an invalid hex element") { + let testHexElement = "X" + + beforeEach { + _ = sut.textField(textField, shouldChangeCharactersIn: NSRange(location: 0, length: 1), replacementString: testHexElement) + } + + it("does not set the text field's text to the new value") { + expect(textField.text).to(beEmpty()) + } + + it("does not change the first responder") { + expect(textField.isFirstResponder).to(beTrue()) + } + + it("generates a haptic error feedback") { + expect(hapticFeedbackProviderMock.didCallGenerateErrorFeedback).to(beTrue()) + } + } + + context("calling unselectTextField") { + beforeEach { + sut.unselectTextField() + } + + it("resets text to last valid value") { + assertSnapshot(matching: sut, as: .image(size: .init(width: testFrame.width, height: testFrame.height)), named: "text_field_unselected") + } + } + } + } + } + } +} diff --git a/Example/Tests/RGBViewModelTests.swift b/Example/Tests/RGBViewModelTests.swift index 54b6490..3b0e641 100644 --- a/Example/Tests/RGBViewModelTests.swift +++ b/Example/Tests/RGBViewModelTests.swift @@ -18,17 +18,13 @@ class RGBViewModelTests: QuickSpec { var delegateMock: SheetyColorsDelegateMock! context("after initialization") { - var testColorModel: RGBAColor! - var testIsAlphaEnabled: Bool! - var testHasTextOrMessage: Bool! + let testColorModel = RGBAColor(red: 10.0, green: 11.0, blue: 12.0, alpha: 13.0) + let testConfig = SheetyColorsConfig(alphaEnabled: true, hapticFeedbackEnabled: true, initialColor: testColorModel.uiColor, title: "title", message: "message", type: .rgb) beforeEach { viewDelegateMock = SheetyColorsViewDelegateMock() delegateMock = SheetyColorsDelegateMock() - testIsAlphaEnabled = true - testHasTextOrMessage = true - testColorModel = RGBAColor(red: 10.0, green: 11.0, blue: 12.0, alpha: 13.0) - sut = RGBViewModel(withColorModel: testColorModel, isAlphaEnabled: testIsAlphaEnabled, hasTextOrMessage: testHasTextOrMessage) + sut = RGBViewModel(withConfig: testConfig) sut.viewDelegate = viewDelegateMock sut.delegate = delegateMock } @@ -39,7 +35,7 @@ class RGBViewModelTests: QuickSpec { context("when calling hasTextOrMessage property") { it("returns the correct state") { - expect(sut!.hasTextOrMessage).to(equal(testHasTextOrMessage)) + expect(sut!.hasTextOrMessage).to(beTrue()) } } diff --git a/Example/Tests/SheetyColorsViewDelegateMock.swift b/Example/Tests/SheetyColorsViewDelegateMock.swift index aa931a2..a0b1179 100644 --- a/Example/Tests/SheetyColorsViewDelegateMock.swift +++ b/Example/Tests/SheetyColorsViewDelegateMock.swift @@ -12,7 +12,7 @@ import Foundation class SheetyColorsViewDelegateMock: SheetyColorsViewDelegate { var didCallDidUpdateColorComponent = false - func didUpdateColorComponent(in _: SheetyColorsViewModelProtocol) { + func didUpdateColorComponent(in _: SheetyColorsViewModelProtocol, shouldAnimate _: Bool) { didCallDidUpdateColorComponent = true } } diff --git a/Example/Tests/SheetyColorsViewFactoryTests.swift b/Example/Tests/SheetyColorsViewFactoryTests.swift index 92ccb9e..a72d471 100644 --- a/Example/Tests/SheetyColorsViewFactoryTests.swift +++ b/Example/Tests/SheetyColorsViewFactoryTests.swift @@ -103,8 +103,8 @@ class SheetyColorsViewFactoryTests: QuickSpec { view = SheetyColorsViewFactory.createView(withConfig: testConfig) } - it("sets hapticFeedbackEnabled on the view controller to true") { - expect(view.hapticFeedbackEnabled).to(beTrue()) + it("sets the HapticFeedbackProvider instance") { + expect(view.hapticFeedbackProvider).toNot(beNil()) } } @@ -114,8 +114,8 @@ class SheetyColorsViewFactoryTests: QuickSpec { view = SheetyColorsViewFactory.createView(withConfig: testConfig) } - it("sets hapticFeedbackEnabled on the view controller to false") { - expect(view.hapticFeedbackEnabled).to(beFalse()) + it("doesn't set the HapticFeedbackProvider instance") { + expect(view.hapticFeedbackProvider).to(beNil()) } } diff --git a/Example/Tests/SheetyColorsViewModelMock.swift b/Example/Tests/SheetyColorsViewModelMock.swift index 5f982e7..25a922c 100644 --- a/Example/Tests/SheetyColorsViewModelMock.swift +++ b/Example/Tests/SheetyColorsViewModelMock.swift @@ -17,6 +17,7 @@ class SheetyColorsViewModelMock: SheetyColorsViewModelProtocol { var secondaryKeyText: String = "" var secondaryValueText: String = "" var hasTextOrMessage: Bool = false + var isHapticFeedbackEnabled: Bool = false var previewColorModel: SheetyColorProtocol = RGBAColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) var numberOfSliders: Int = 0 @@ -53,4 +54,6 @@ class SheetyColorsViewModelMock: SheetyColorsViewModelProtocol { } func sliderValueChanged(forSliderAt _: Int, value _: CGFloat) {} + + func hexValueChanged(withColor _: UIColor) {} } diff --git a/Example/Tests/SheetyColorsViewTests.swift b/Example/Tests/SheetyColorsViewTests.swift index ccbbbbe..c060682 100644 --- a/Example/Tests/SheetyColorsViewTests.swift +++ b/Example/Tests/SheetyColorsViewTests.swift @@ -15,141 +15,132 @@ class SheetyColorsViewTests: QuickSpec { override func spec() { describe("The SheetyColorsViewController") { var sut: SheetyColorsViewController! + let testColor = UIColor(red: 0.0, green: 0.25, blue: 0.5, alpha: 0.75) + var testConfig = SheetyColorsConfig(alphaEnabled: false, hapticFeedbackEnabled: false, initialColor: testColor, title: nil, message: nil, type: .rgb) - context("when RGB SheetyColors view is configured with alpha enabled") { + context("when RGB SheetyColors view is configured with alpha disabled") { beforeEach { - let testColor = UIColor(red: 0.0, green: 0.25, blue: 0.5, alpha: 0.75).rgbaColor - let viewModel = RGBViewModel(withColorModel: testColor, isAlphaEnabled: false, hasTextOrMessage: true) - sut = SheetyColorsViewController.create() - sut.viewModel = viewModel - sut.hapticFeedbackEnabled = false + testConfig.alphaEnabled = false + testConfig.type = .rgb + let viewModel = RGBViewModel(withConfig: testConfig) + sut = SheetyColorsViewController(viewModel: viewModel) viewModel.viewDelegate = sut } it("renders a RGB SheetyColors view without an alpha slider") { - assertSnapshot(matching: sut, as: .recursiveDescription(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "rgb_without_alpha_normal") + assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400)), named: "rgb_without_alpha_wide") } -// context("when start dragging a slider") { -// var testSlider: GradientSlider! -// -// beforeEach { -// testSlider = GradientSlider(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)) -// sut.sliderDidStartEditing(testSlider) -// } -// -// it("it unhides the labels") { -// assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) -// } -// -// context("when changing a slider's position") { -// beforeEach { -// let activeSlider = sut.sliders[0] -// activeSlider.value = 255.0 -// sut.sliderValueDidChange(activeSlider) -// } -// -// it("it changes colors and labels accordingly") { -// assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) -// } -// } -// -// context("when lifting the finger after sliding") { -// beforeEach { -// sut.sliderDidStartEditing(testSlider) -// } -// -// it("it hides the labels again") { -// assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) -// } -// } -// } + context("when start dragging a slider") { + var testSlider: GradientSlider! + + beforeEach { + testSlider = GradientSlider(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)) + sut.viewDidLoad() + sut.sliderDidStartEditing(testSlider) + } + + it("it unhides the labels") { + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "rgb_dragging_slider_displays_labels") + } + + context("when changing a slider's position") { + beforeEach { + let activeSlider = sut.sliders[0] + activeSlider.value = 255.0 + sut.sliderValueDidChange(activeSlider) + } + + it("it changes colors and labels accordingly") { + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "rgb_dragging_slider_changes_values") + } + } + + context("when lifting the finger after sliding") { + beforeEach { + sut.sliderDidStartEditing(testSlider) + } + + it("it hides the labels again") { + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "rgb_stop_dragging_hides_labels") + } + } + } } - context("when RGB SheetyColors view is configured with alpha disabled") { + context("when RGB SheetyColors view is configured with alpha enabled") { beforeEach { - let testColor = UIColor(red: 0.0, green: 0.25, blue: 0.5, alpha: 0.75).rgbaColor - let viewModel = RGBViewModel(withColorModel: testColor, isAlphaEnabled: true, hasTextOrMessage: true) - sut = SheetyColorsViewController.create() - sut.viewModel = viewModel - sut.hapticFeedbackEnabled = false + testConfig.alphaEnabled = true + testConfig.type = .rgb + let viewModel = RGBViewModel(withConfig: testConfig) + sut = SheetyColorsViewController(viewModel: viewModel) viewModel.viewDelegate = sut } it("renders a RGB SheetyColors view with an alpha slider") { - assertSnapshot(matching: sut, as: .recursiveDescription(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "rgb_with_alpha_normal") + assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400)), named: "rgb_with_alpha_wide") } } context("when HSB SheetyColors view is configured with alpha disabled") { beforeEach { - let testColor = UIColor(red: 0.0, green: 0.25, blue: 0.5, alpha: 0.75).hsbaColor - let viewModel = HSBViewModel(withColorModel: testColor, isAlphaEnabled: false, hasTextOrMessage: true) - sut = SheetyColorsViewController.create() - sut.viewModel = viewModel - sut.hapticFeedbackEnabled = false + testConfig.alphaEnabled = false + testConfig.type = .hsb + let viewModel = HSBViewModel(withConfig: testConfig) + sut = SheetyColorsViewController(viewModel: viewModel) viewModel.viewDelegate = sut } it("renders a HSB SheetyColors view without an alpha slider") { - assertSnapshot(matching: sut, as: .recursiveDescription(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "hsb_without_alpha_normal") + assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400)), named: "hsb_without_alpha_wide") } } context("when HSB SheetyColors view is configured with alpha enabled") { beforeEach { - let testColor = UIColor(red: 0.0, green: 0.25, blue: 0.5, alpha: 0.75).hsbaColor - let viewModel = HSBViewModel(withColorModel: testColor, isAlphaEnabled: true, hasTextOrMessage: true) - sut = SheetyColorsViewController.create() - sut.viewModel = viewModel - sut.hapticFeedbackEnabled = false + testConfig.alphaEnabled = true + testConfig.type = .hsb + let viewModel = HSBViewModel(withConfig: testConfig) + sut = SheetyColorsViewController(viewModel: viewModel) viewModel.viewDelegate = sut } it("renders a HSB SheetyColors view with an alpha slider") { - assertSnapshot(matching: sut, as: .recursiveDescription(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "hsb_with_alpha_normal") + assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400)), named: "hsb_with_alpha_wide") } } context("when Grayscale SheetyColors view is configured with alpha disabled") { beforeEach { - let testColor = UIColor(white: 0.5, alpha: 0.75).grayscaleColor - let viewModel = GrayscaleViewModel(withColorModel: testColor, isAlphaEnabled: false, hasTextOrMessage: true) - sut = SheetyColorsViewController.create() - sut.viewModel = viewModel - sut.hapticFeedbackEnabled = false + testConfig.alphaEnabled = false + testConfig.type = .grayscale + let viewModel = GrayscaleViewModel(withConfig: testConfig) + sut = SheetyColorsViewController(viewModel: viewModel) viewModel.viewDelegate = sut } it("renders a Grayscale SheetyColors view without an alpha slider") { - assertSnapshot(matching: sut, as: .recursiveDescription(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "grayscale_without_alpha_normal") + assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400)), named: "grayscale_without_alpha_wide") } } context("when Grayscale SheetyColors view is configured with alpha enabled") { beforeEach { - let testColor = UIColor(white: 0.5, alpha: 0.75).grayscaleColor - let viewModel = GrayscaleViewModel(withColorModel: testColor, isAlphaEnabled: true, hasTextOrMessage: true) - sut = SheetyColorsViewController.create() - sut.viewModel = viewModel - sut.hapticFeedbackEnabled = false + testConfig.alphaEnabled = true + testConfig.type = .grayscale + let viewModel = GrayscaleViewModel(withConfig: testConfig) + sut = SheetyColorsViewController(viewModel: viewModel) viewModel.viewDelegate = sut } it("renders a Grayscale SheetyColors view with an alpha slider") { - assertSnapshot(matching: sut, as: .recursiveDescription(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400))) - assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) + assertSnapshot(matching: sut, as: .image(size: .init(width: 300, height: 400)), named: "grayscale_with_alpha_normal") + assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400)), named: "grayscale_with_alpha_wide") } } } diff --git a/Example/Tests/UIColor+hexTests.swift b/Example/Tests/UIColor+hexTests.swift new file mode 100644 index 0000000..94d2766 --- /dev/null +++ b/Example/Tests/UIColor+hexTests.swift @@ -0,0 +1,29 @@ +// +// UIColor+hexTests.swift +// SheetyColors_Tests +// +// Created by Wendt, Christoph on 13.04.20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Nimble +import Quick + +class UIColorHexTests: QuickSpec { + override func spec() { + describe("The UIColor") { + context("when initialized with a valid hex string") { + it("creates the correct color instance") { + let expectedColor = UIColor(red: 250 / 255.0, green: 128 / 255.0, blue: 114 / 255.0, alpha: 1.0) + expect(UIColor(hex: "FA8072")).to(equal(expectedColor)) + } + } + + context("when initialized with an invalid hex string") { + it("returns nil") { + expect(UIColor(hex: "XXXXXX")).to(beNil()) + } + } + } + } +} diff --git a/Example/Tests/__Snapshots__/GradientSliderTests/spec.1.png b/Example/Tests/__Snapshots__/GradientSliderTests/spec.1.png deleted file mode 100644 index 73d495f..0000000 Binary files a/Example/Tests/__Snapshots__/GradientSliderTests/spec.1.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/GradientSliderTests/spec.2.txt b/Example/Tests/__Snapshots__/GradientSliderTests/spec.2.txt deleted file mode 100644 index f90c2b7..0000000 --- a/Example/Tests/__Snapshots__/GradientSliderTests/spec.2.txt +++ /dev/null @@ -1,5 +0,0 @@ -> - | (layer) - | (layer) - | | (layer) - | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) \ No newline at end of file diff --git a/Example/Tests/__Snapshots__/GradientSliderTests/spec.3.png b/Example/Tests/__Snapshots__/GradientSliderTests/spec.3.png deleted file mode 100644 index 994f46f..0000000 Binary files a/Example/Tests/__Snapshots__/GradientSliderTests/spec.3.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/GradientSliderTests/spec.initial_custom_state.png b/Example/Tests/__Snapshots__/GradientSliderTests/spec.initial_custom_state.png new file mode 100644 index 0000000..5f76147 Binary files /dev/null and b/Example/Tests/__Snapshots__/GradientSliderTests/spec.initial_custom_state.png differ diff --git a/Example/Tests/__Snapshots__/GradientSliderTests/spec.initial_default_state.png b/Example/Tests/__Snapshots__/GradientSliderTests/spec.initial_default_state.png new file mode 100644 index 0000000..b11a65c Binary files /dev/null and b/Example/Tests/__Snapshots__/GradientSliderTests/spec.initial_default_state.png differ diff --git a/Example/Tests/__Snapshots__/HexTextFieldTests/spec.initial_state.png b/Example/Tests/__Snapshots__/HexTextFieldTests/spec.initial_state.png new file mode 100644 index 0000000..28bb9fb Binary files /dev/null and b/Example/Tests/__Snapshots__/HexTextFieldTests/spec.initial_state.png differ diff --git a/Example/Tests/__Snapshots__/HexTextFieldTests/spec.text_field_selected.png b/Example/Tests/__Snapshots__/HexTextFieldTests/spec.text_field_selected.png new file mode 100644 index 0000000..8d9d384 Binary files /dev/null and b/Example/Tests/__Snapshots__/HexTextFieldTests/spec.text_field_selected.png differ diff --git a/Example/Tests/__Snapshots__/HexTextFieldTests/spec.text_field_unselected.png b/Example/Tests/__Snapshots__/HexTextFieldTests/spec.text_field_unselected.png new file mode 100644 index 0000000..28bb9fb Binary files /dev/null and b/Example/Tests/__Snapshots__/HexTextFieldTests/spec.text_field_unselected.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.1.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.1.txt deleted file mode 100644 index a6b2480..0000000 --- a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.1.txt +++ /dev/null @@ -1,29 +0,0 @@ -> - | > - | | ; layer = > - | | | (layer) - | | | (layer) - | | | > - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | > - | | | | > - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) \ No newline at end of file diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.10.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.10.txt deleted file mode 100644 index 573bfd5..0000000 --- a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.10.txt +++ /dev/null @@ -1,34 +0,0 @@ -> - | > - | | ; layer = > - | | | (layer) - | | | (layer) - | | | > - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | > - | | | | > - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) \ No newline at end of file diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.11.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.11.png deleted file mode 100644 index 6957601..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.11.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.12.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.12.png deleted file mode 100644 index e14d810..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.12.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.13.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.13.txt deleted file mode 100644 index 37ce7af..0000000 --- a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.13.txt +++ /dev/null @@ -1,19 +0,0 @@ -> - | > - | | ; layer = > - | | | (layer) - | | | (layer) - | | | > - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | > - | | | | > - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) \ No newline at end of file diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.14.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.14.png deleted file mode 100644 index e071fb9..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.14.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.15.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.15.png deleted file mode 100644 index 597bb5f..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.15.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.16.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.16.txt deleted file mode 100644 index b524ad1..0000000 --- a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.16.txt +++ /dev/null @@ -1,24 +0,0 @@ -> - | > - | | ; layer = > - | | | (layer) - | | | (layer) - | | | > - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | > - | | | | > - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) \ No newline at end of file diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.17.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.17.png deleted file mode 100644 index a6dc0ff..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.17.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.18.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.18.png deleted file mode 100644 index 6e12da6..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.18.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.2.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.2.png deleted file mode 100644 index 51e0aec..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.2.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.3.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.3.png deleted file mode 100644 index 86de528..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.3.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.4.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.4.txt deleted file mode 100644 index f2f7ddf..0000000 --- a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.4.txt +++ /dev/null @@ -1,34 +0,0 @@ -> - | > - | | ; layer = > - | | | (layer) - | | | (layer) - | | | > - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | > - | | | | > - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) \ No newline at end of file diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.5.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.5.png deleted file mode 100644 index f1d87d5..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.5.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.6.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.6.png deleted file mode 100644 index 775f93f..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.6.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.7.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.7.txt deleted file mode 100644 index c8d5db8..0000000 --- a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.7.txt +++ /dev/null @@ -1,29 +0,0 @@ -> - | > - | | ; layer = > - | | | (layer) - | | | (layer) - | | | > - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | | > - | | | | | ; }; layer = <_UILabelLayer>> - | | | | | ; }; layer = <_UILabelLayer>> - | | | > - | | | | > - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) - | | > - | | | (layer) - | | | (layer) - | | | | (layer) - | | | | <_TtC12SheetyColorsP33_484746A459ABDEEC544DEF4AF835257327VerticallyCenteredTextLayer> (layer) \ No newline at end of file diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.8.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.8.png deleted file mode 100644 index d450f6a..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.8.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.9.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.9.png deleted file mode 100644 index 0a2c856..0000000 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.9.png and /dev/null differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_with_alpha_normal.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_with_alpha_normal.png new file mode 100644 index 0000000..4ffd133 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_with_alpha_normal.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_with_alpha_wide.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_with_alpha_wide.png new file mode 100644 index 0000000..92c9931 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_with_alpha_wide.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_without_alpha_normal.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_without_alpha_normal.png new file mode 100644 index 0000000..1c669be Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_without_alpha_normal.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_without_alpha_wide.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_without_alpha_wide.png new file mode 100644 index 0000000..9b6f79e Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.grayscale_without_alpha_wide.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_with_alpha_normal.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_with_alpha_normal.png new file mode 100644 index 0000000..3d90206 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_with_alpha_normal.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_with_alpha_wide.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_with_alpha_wide.png new file mode 100644 index 0000000..40daea5 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_with_alpha_wide.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_without_alpha_normal.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_without_alpha_normal.png new file mode 100644 index 0000000..db2492d Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_without_alpha_normal.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_without_alpha_wide.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_without_alpha_wide.png new file mode 100644 index 0000000..c8b713a Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.hsb_without_alpha_wide.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_dragging_slider_changes_values.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_dragging_slider_changes_values.png new file mode 100644 index 0000000..c3b6d00 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_dragging_slider_changes_values.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_dragging_slider_displays_labels.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_dragging_slider_displays_labels.png new file mode 100644 index 0000000..988e279 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_dragging_slider_displays_labels.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_stop_dragging_hides_labels.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_stop_dragging_hides_labels.png new file mode 100644 index 0000000..988e279 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_stop_dragging_hides_labels.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_with_alpha_normal.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_with_alpha_normal.png new file mode 100644 index 0000000..1f6a061 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_with_alpha_normal.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_with_alpha_wide.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_with_alpha_wide.png new file mode 100644 index 0000000..1189183 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_with_alpha_wide.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_without_alpha_normal.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_without_alpha_normal.png new file mode 100644 index 0000000..e03d0d3 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_without_alpha_normal.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_without_alpha_wide.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_without_alpha_wide.png new file mode 100644 index 0000000..996d9aa Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.rgb_without_alpha_wide.png differ diff --git a/README.md b/README.md index 07579bc..be95de6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Cocoapods(https://cocoapods.org/pods/SheetyColors)](https://img.shields.io/cocoapods/v/SheetyColors.svg) [![Carthage compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage) -![SwiftUI Ready](https://img.shields.io/badge/SwiftUI-Ready-yellow) +![UIKit & SwiftUI](https://img.shields.io/badge/iOS-UIKit%20%26%20SwiftUI-yellow) ![iOS 13 Ready](https://img.shields.io/badge/iOS%2013-Ready-blueviolet) ![Swift](https://img.shields.io/badge/swift-5.0-red.svg) [![Build Status](https://app.bitrise.io/app/e955e72e7da4b8c0/status.svg?token=wOm6zBpCFw7ZeP8gJdDE_A&branch=develop)](https://app.bitrise.io/app/e955e72e7da4b8c0) @@ -12,16 +12,21 @@ **SheetyColors** is an action sheet styled color picker for iOS: -- **Based on UIAlertController:** The SheetyColors API is based on UIKit's `UIAlertController`. Simply add buttons to it as you would for any other Action Sheet by defining `UIAlertAction` instances. Therefore, it nicely integrates with the look & feel of all other native system dialogs. However, you can also chose to use the color picker it self without an action sheet. -- **SwiftUI support:** SheetyColors can also be used as part of your SwiftUI projects. Have a look at the *Usage* section to get further info. -- **iOS 13 ready:** SheetyColors has been tested on devices running iOS 13 Beta. The library is also optimized to work well with the new Dark Mode. -- **Fully configurable:** You can choose between a variety of configurations such as a color model (RGB, HSB, or Grayscale), alpha component support, haptic feedback, text/message label, and many more. -- **Intuitive UI:** Each slider comes with a gradient that gives you an idea of how changing individual slider values affects the resulting color. - -|RGB, HSB, and Grayscale|Fully configurable|Dark mode support| -| :-: | :-: | :-: | -|![Color picker supporting RGB, HSB, and Grayscale][minimum_config]|![Fully configurable][fully_configurable]|![Dark mode support][dark_mode]| - +- 📱 **Based on UIAlertController:** The SheetyColors API is based on UIKit's `UIAlertController`. Simply add buttons to it as you would for any other Action Sheet by defining `UIAlertAction` instances. Therefore, it nicely integrates with the look & feel of all other native system dialogs. However, you can also chose to use the color picker it self without an action sheet. +- 🎨 **Fully configurable:** You can choose between a variety of configurations such as + - color model (RGB, HSB, or Grayscale) + - alpha component support + - haptic feedback + - text/message label +- 🎚️ **Sliders and Hex input:** You can create new colors by either using sliders or the newly added Hex input. +- 👶 **Intuitive UI:** Each slider comes with a gradient that gives you an idea of how changing individual slider values affects the resulting color. All controls do support haptic feedback and will react to any errors such as invalid Hex values. +- 🍏 **SwiftUI & iOS 13 support:** SheetyColors can also be used as part of your SwiftUI projects. Have a look at the *Usage* section to get further info. The library is also optimized to work well with the new Dark Mode. + +| NEW: Hex input |RGB, HSB, and Grayscale|Fully configurable|Dark mode support| +| :-: | :-: | :-: | :-: | +|![Color picker supporting RGB, HSB, and Grayscale][hex_input]|![Color picker supporting RGB, HSB, and Grayscale][minimum_config]|![Fully configurable][fully_configurable]|![Dark mode support][dark_mode]| + +[hex_input]: ./Documentation/hex_input.gif "New Hex input field" [minimum_config]: ./Documentation/demo_minimum_configuration.png "Color picker supporting RGB, HSB, and Grayscale" [fully_configurable]: ./Documentation/demo_customizable.png "Fully configurable" [dark_mode]: ./Documentation/demo_dark_mode.png "Dark mode support" @@ -49,7 +54,7 @@ end ```ruby dependencies: [ - .package(url: "https://github.com/chrs1885/SheetyColors.git", from: "1.1.0") + .package(url: "https://github.com/chrs1885/SheetyColors.git", from: "1.2.0") ] ``` @@ -98,7 +103,7 @@ present(sheetyColors, animated: true, completion: nil) Please check the [documentation](./Documentation/Reference/README.md) for further information on the API. ### Custom container views -If you prefer to use the color picker inside a custom view controller, you can do so by doing creating the picker's view controller directly: +If you prefer to use the color picker inside a custom view controller, you can do so by creating the picker's view controller directly: ```swift return SheetyColorsViewFactory.createView(withConfig: config, delegate: myDelegate) @@ -131,7 +136,7 @@ struct ContentView: View { var body: some View { Text("Select a color") .foregroundColor(Color(self.$selectedColor.wrappedValue)) - ColorPicker(config: config, color: self.$selectedColor) + SheetyColorsView(config: config, color: self.$selectedColor) } } ``` diff --git a/SheetyColors.podspec b/SheetyColors.podspec index 84e27be..e6669ed 100644 --- a/SheetyColors.podspec +++ b/SheetyColors.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SheetyColors' - s.version = '1.1.0' + s.version = '1.2.0' s.summary = 'An action sheet styled color picker for iOS.' s.description = <<-DESC diff --git a/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift b/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift index 9cb0a3a..f2494fc 100644 --- a/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift +++ b/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift @@ -20,20 +20,17 @@ public struct SheetyColorsViewFactory { */ public static func createView(withConfig config: SheetyColorsConfigProtocol, delegate: SheetyColorsDelegate? = nil) -> SheetyColorsViewController { var viewModel: SheetyColorsViewModelProtocol - let hasTextOrMessage: Bool = config.title != nil || config.message != nil switch config.type { case .grayscale: - viewModel = GrayscaleViewModel(withColorModel: config.initialColor.grayscaleColor, isAlphaEnabled: config.alphaEnabled, hasTextOrMessage: hasTextOrMessage) + viewModel = GrayscaleViewModel(withConfig: config) case .hsb: - viewModel = HSBViewModel(withColorModel: config.initialColor.hsbaColor, isAlphaEnabled: config.alphaEnabled, hasTextOrMessage: hasTextOrMessage) + viewModel = HSBViewModel(withConfig: config) case .rgb: - viewModel = RGBViewModel(withColorModel: config.initialColor.rgbaColor, isAlphaEnabled: config.alphaEnabled, hasTextOrMessage: hasTextOrMessage) + viewModel = RGBViewModel(withConfig: config) } - let viewController = SheetyColorsViewController.create() - viewController.viewModel = viewModel - viewController.hapticFeedbackEnabled = config.hapticFeedbackEnabled + let viewController = SheetyColorsViewController(viewModel: viewModel) viewModel.viewDelegate = viewController viewModel.delegate = delegate diff --git a/SheetyColors/Classes/Common/Models/Extensions/UIColor+hex.swift b/SheetyColors/Classes/Common/Models/Extensions/UIColor+hex.swift new file mode 100644 index 0000000..4c3498e --- /dev/null +++ b/SheetyColors/Classes/Common/Models/Extensions/UIColor+hex.swift @@ -0,0 +1,33 @@ +// +// UIColor+hex.swift +// SheetyColors +// +// Created by Wendt, Christoph on 04.04.20. +// + +import Foundation + +extension UIColor { + public convenience init?(hex: String) { + let r, g, b: CGFloat + + let start = hex.index(hex.startIndex, offsetBy: 0) + let hexColor = String(hex[start...]) + + if hexColor.count == 6 { + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + r = CGFloat((hexNumber & 0xFF0000) >> 16) / 255 + g = CGFloat((hexNumber & 0x00FF00) >> 8) / 255 + b = CGFloat(hexNumber & 0x0000FF) / 255 + + self.init(red: r, green: g, blue: b, alpha: 1.0) + return + } + } + + return nil + } +} diff --git a/SheetyColors/Classes/Common/Models/HapticFeedbackProvider.swift b/SheetyColors/Classes/Common/Models/HapticFeedbackProvider.swift new file mode 100644 index 0000000..f283cfc --- /dev/null +++ b/SheetyColors/Classes/Common/Models/HapticFeedbackProvider.swift @@ -0,0 +1,35 @@ +// +// HapticFeedbackProvider.swift +// SheetyColors +// +// Created by Wendt, Christoph on 10.04.20. +// + +import Foundation + +class HapticFeedbackProvider: HapticFeedbackProviderProtocol { + private var selectionFeedback: UISelectionFeedbackGenerator? + + func generateInputFeedback() { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + } + + func generateSelectionFeedback() { + if selectionFeedback == nil { + selectionFeedback = UISelectionFeedbackGenerator() + } + + selectionFeedback?.prepare() + selectionFeedback?.selectionChanged() + } + + func generateErrorFeedback() { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) + } + + func resetSelectionFeedback() { + selectionFeedback = nil + } +} diff --git a/SheetyColors/Classes/Common/Models/Protocols/HapticFeedbackProviderProtocol.swift b/SheetyColors/Classes/Common/Models/Protocols/HapticFeedbackProviderProtocol.swift new file mode 100644 index 0000000..759c25a --- /dev/null +++ b/SheetyColors/Classes/Common/Models/Protocols/HapticFeedbackProviderProtocol.swift @@ -0,0 +1,15 @@ +// +// HapticFeedbackProviderProtocol.swift +// SheetyColors +// +// Created by Wendt, Christoph on 10.04.20. +// + +import Foundation + +protocol HapticFeedbackProviderProtocol { + func generateInputFeedback() + func generateSelectionFeedback() + func generateErrorFeedback() + func resetSelectionFeedback() +} diff --git a/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewDelegate.swift b/SheetyColors/Classes/Common/ViewModels/Protocols/SheetyColorsViewDelegate.swift similarity index 86% rename from SheetyColors/Classes/Common/ViewModels/SheetyColorsViewDelegate.swift rename to SheetyColors/Classes/Common/ViewModels/Protocols/SheetyColorsViewDelegate.swift index a66a475..87af057 100644 --- a/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewDelegate.swift +++ b/SheetyColors/Classes/Common/ViewModels/Protocols/SheetyColorsViewDelegate.swift @@ -6,5 +6,5 @@ // protocol SheetyColorsViewDelegate: AnyObject { - func didUpdateColorComponent(in viewModel: SheetyColorsViewModelProtocol) + func didUpdateColorComponent(in viewModel: SheetyColorsViewModelProtocol, shouldAnimate: Bool) } diff --git a/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewModelProtocol.swift b/SheetyColors/Classes/Common/ViewModels/Protocols/SheetyColorsViewModelProtocol.swift similarity index 92% rename from SheetyColors/Classes/Common/ViewModels/SheetyColorsViewModelProtocol.swift rename to SheetyColors/Classes/Common/ViewModels/Protocols/SheetyColorsViewModelProtocol.swift index 81ce286..acc073b 100644 --- a/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewModelProtocol.swift +++ b/SheetyColors/Classes/Common/ViewModels/Protocols/SheetyColorsViewModelProtocol.swift @@ -10,6 +10,7 @@ import CoreGraphics protocol SheetyColorsViewModelProtocol { var viewDelegate: SheetyColorsViewDelegate? { get set } var delegate: SheetyColorsDelegate? { get set } + var isHapticFeedbackEnabled: Bool { get } var hasTextOrMessage: Bool { get } var primaryKeyText: String { get } var primaryValueText: String { get } @@ -27,4 +28,5 @@ protocol SheetyColorsViewModelProtocol { func thumbText(forSliderAt index: Int) -> String? func thumbIconName(forSliderAt index: Int) -> String? func sliderValueChanged(forSliderAt index: Int, value: CGFloat) + func hexValueChanged(withColor color: UIColor) } diff --git a/SheetyColors/Classes/Common/Views/HexTextField.swift b/SheetyColors/Classes/Common/Views/HexTextField.swift new file mode 100644 index 0000000..961cd2a --- /dev/null +++ b/SheetyColors/Classes/Common/Views/HexTextField.swift @@ -0,0 +1,242 @@ +// +// HexTextField.swift +// SheetyColors +// +// Created by Wendt, Christoph on 05.04.20. +// + +import UIKit + +class HexTextField: UIView { + private struct Constants { + static let textFieldHeight: CGFloat = 17.0 + static let textFieldWidth: CGFloat = 15.0 + static let letterSpacing: CGFloat = 4.0 + static let borderWidth: CGFloat = 1.0 + static let numberOfTextFields = 6 + static let font: UIFont = UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: .light) + static var componentWidth: CGFloat { + CGFloat(numberOfTextFields) * textFieldWidth + CGFloat(numberOfTextFields - 1) * letterSpacing + } + } + + private var button: UIButton! + private var textFieldStackView: UIStackView! + private var underlineViews = [UIView]() + private var lastHexValue: String? + private var hapticFeedbackProvider: HapticFeedbackProviderProtocol? + var textFields = [UITextField]() + + weak var delegate: HexTextFieldDelegate? + var textColor: UIColor = .white { + didSet { + for i in 0 ..< Constants.numberOfTextFields { + textFields[i].textColor = textColor + underlineViews[i].backgroundColor = textColor + } + } + } + + var text: String { + set { + let hexElements = Array(newValue) + for i in 0 ..< Constants.numberOfTextFields { + textFields[i].text = String(hexElements[i]) + } + lastHexValue = newValue + } + + get { + return textFields.compactMap { $0.text }.joined() + } + } + + init(hapticFeedbackProvider: HapticFeedbackProviderProtocol? = nil) { + super.init(frame: CGRect.zero) + self.hapticFeedbackProvider = hapticFeedbackProvider + setupViews() + layoutViews() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Setup & Layout + +extension HexTextField { + private func setupViews() { + for _ in 1 ... Constants.numberOfTextFields { + setupTextField() + } + + button = UIButton(frame: .zero) + button.backgroundColor = .clear + button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) + } + + private func setupTextField() { + let textField = UITextField() + textField.delegate = self + textField.textAlignment = .center + textField.textColor = textColor + textField.font = Constants.font + textField.tintColor = .clear + textFields.append(textField) + + let underlineView = UIView(frame: .zero) + underlineView.backgroundColor = textColor + underlineViews.append(underlineView) + } + + private func layoutViews() { + textFieldStackView = UIStackView() + textFieldStackView.axis = .horizontal + textFieldStackView.spacing = Constants.letterSpacing + addSubview(textFieldStackView) + textFieldStackView.anchor(top: topAnchor, bottom: bottomAnchor, left: leftAnchor, right: rightAnchor, widthConstant: Constants.componentWidth) + + for i in 0 ..< Constants.numberOfTextFields { + layoutTextField(withIndex: i) + } + + addSubview(button) + button.anchor(top: textFieldStackView.topAnchor, bottom: textFieldStackView.bottomAnchor, left: textFieldStackView.leftAnchor, right: textFieldStackView.rightAnchor) + } + + private func layoutTextField(withIndex index: Int) { + let textField = textFields[index] + let underlineView = underlineViews[index] + + let letterStackView = UIStackView(arrangedSubviews: [textField, underlineView]) + letterStackView.axis = .vertical + letterStackView.spacing = 2.0 + textField.anchor(widthConstant: Constants.textFieldWidth, heightConstant: Constants.textFieldHeight) + underlineView.anchor(width: textField.widthAnchor, heightConstant: Constants.borderWidth) + + textFieldStackView.addArrangedSubview(letterStackView) + } +} + +// MARK: - Text Field Highlighting + +extension HexTextField { + @objc func buttonPressed() { + textFields.first?.becomeFirstResponder() + setSelectedTextField(at: 0) + for element in textFields { + element.text = "" + } + } + + func unselectTextField() { + if text.lengthOfBytes(using: .utf8) != Constants.numberOfTextFields, let lastValue = lastHexValue { + text = lastValue + } + + for textField in textFields { + if textField.isFirstResponder { + textField.resignFirstResponder() + } + } + } + + private func setSelectedTextField(at index: Int?) { + for i in 0 ..< Constants.numberOfTextFields { + let underline = underlineViews[i] + + if i == index { + underline.blink() + } else { + underline.layer.removeAllAnimations() + } + } + } +} + +// MARK: - UITextFieldDelegate + +extension HexTextField: UITextFieldDelegate { + func textFieldDidEndEditing(_: UITextField) { + setSelectedTextField(at: nil) + delegate?.hexTextField(self, didEditHexValue: text) + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn _: NSRange, replacementString string: String) -> Bool { + if !string.isEmpty { + if string.isHex { + handleInsertion(currentTextField: textField, text: string.uppercased()) + hapticFeedbackProvider?.generateInputFeedback() + } else { + textFieldStackView.shake() + hapticFeedbackProvider?.generateErrorFeedback() + } + } else { + handleDeletion(currentTextField: textField) + hapticFeedbackProvider?.generateInputFeedback() + } + + return false + } + + private func handleInsertion(currentTextField: UITextField, text: String) { + guard let currentIndex = textFields.firstIndex(of: currentTextField) else { return } + + let nextIndex = currentIndex + 1 + + if currentIndex == 0, !currentTextField.hasText { + currentTextField.text = text + setSelectedTextField(at: nextIndex) + } else { + textFields[nextIndex].becomeFirstResponder() + textFields[nextIndex].text = text + if nextIndex == Constants.numberOfTextFields - 1 { + textFields[nextIndex].resignFirstResponder() + } else { + setSelectedTextField(at: nextIndex + 1) + } + } + } + + private func handleDeletion(currentTextField: UITextField) { + guard let currentIndex = textFields.firstIndex(of: currentTextField) else { return } + + if currentIndex > 0 { + let previousTextField = textFields[currentIndex - 1] + previousTextField.becomeFirstResponder() + } + + currentTextField.text = "" + setSelectedTextField(at: currentIndex) + } +} + +// MARK: - Validation + +private extension String { + var isHex: Bool { + let hexRegex = try! NSRegularExpression(pattern: "^[0-9a-fA-F]{\(count)}$") + let range = NSRange(location: 0, length: count) + return hexRegex.firstMatch(in: self, options: [], range: range) != nil + } +} + +// MARK: - Animations + +private extension UIView { + func shake() { + let animation = CABasicAnimation(keyPath: "position") + animation.duration = 0.07 + animation.repeatCount = 3 + animation.autoreverses = true + animation.fromValue = NSValue(cgPoint: CGPoint(x: center.x - 5, y: center.y)) + animation.toValue = NSValue(cgPoint: CGPoint(x: center.x + 5, y: center.y)) + layer.add(animation, forKey: "position") + } + + func blink() { + alpha = 0.0 + UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveLinear, .repeat, .autoreverse], animations: { self.alpha = 1.0 }, completion: nil) + } +} diff --git a/SheetyColors/Classes/Common/Views/PreviewColorView.swift b/SheetyColors/Classes/Common/Views/PreviewColorView.swift index 6274cbf..945829e 100644 --- a/SheetyColors/Classes/Common/Views/PreviewColorView.swift +++ b/SheetyColors/Classes/Common/Views/PreviewColorView.swift @@ -8,35 +8,39 @@ import Capable import UIKit class PreviewColorView: UIView { - var primaryKeyLabel: UILabel! + weak var delegate: PreviewColorViewDelegate? + var primaryTitleLabel: UILabel! var primaryValueLabel: UILabel! - var secondaryKeyLabel: UILabel! - var secondaryValueLabel: UILabel! + var hexTitleLabel: UILabel! + var hexValueTextField: HexTextField! var infoButton: UIButton! var labelStackView: UIStackView! var colorLayer: CALayer! var transparencyPatternLayer: CALayer! var isColorViewLabelShown: Bool! + var hapticFeedbackProvider: HapticFeedbackProviderProtocol? var color: UIColor = .clear { didSet { colorLayer?.backgroundColor = color.cgColor + hexValueTextField.unselectTextField() updateTextColor() } } var textColor: UIColor = .clear { didSet { - for label in [primaryKeyLabel, primaryValueLabel, secondaryKeyLabel, secondaryValueLabel] { + for label in [primaryTitleLabel, primaryValueLabel, hexTitleLabel] { label?.textColor = textColor } + hexValueTextField.textColor = textColor infoButton.tintColor = textColor } } var primaryKeyText: String = "" { didSet { - primaryKeyLabel.text = primaryKeyText + primaryTitleLabel.text = primaryKeyText } } @@ -46,33 +50,35 @@ class PreviewColorView: UIView { } } - var secondaryKeyText: String = "" { + var hexKeyText: String = "" { didSet { - secondaryKeyLabel.text = secondaryKeyText + hexTitleLabel.text = hexKeyText } } - var secondaryValueText: String = "" { + var hexValueText: String = "" { didSet { - secondaryValueLabel.text = secondaryValueText + hexValueTextField.text = hexValueText } } - convenience init(withColor color: UIColor) { + convenience init(withColor color: UIColor, hapticFeedbackProvider: HapticFeedbackProviderProtocol? = nil) { self.init(frame: .zero) self.color = color + self.hapticFeedbackProvider = hapticFeedbackProvider colorLayer.backgroundColor = self.color.cgColor updateTextColor() } override init(frame: CGRect) { super.init(frame: frame) - isColorViewLabelShown = false + isColorViewLabelShown = true setupColorView() setupLabels() setupButton() - setupConstraints() + setupLayout() setupGestureRecognizer() + setupTextFieldHandler() updateLabelVisibility(withDuration: 0.0) } @@ -101,34 +107,16 @@ class PreviewColorView: UIView { } private func setupLabels() { - primaryKeyLabel = UILabel(frame: .zero) + primaryTitleLabel = UILabel(frame: .zero) primaryValueLabel = UILabel(frame: .zero) - secondaryKeyLabel = UILabel(frame: .zero) - secondaryValueLabel = UILabel(frame: .zero) + hexTitleLabel = UILabel(frame: .zero) + hexValueTextField = HexTextField(hapticFeedbackProvider: hapticFeedbackProvider) - let keyLabels = [primaryKeyLabel, secondaryKeyLabel] - let valueLabels = [primaryValueLabel, secondaryValueLabel] - - for label in keyLabels { + for label in [primaryTitleLabel, hexTitleLabel] { label?.font = UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: .regular) } - for label in valueLabels { - label?.font = UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: .light) - } - - guard let keyViews = keyLabels as? [UIView], let valueViews = valueLabels as? [UIView] else { return } - - let keyLabelStackView = UIStackView(arrangedSubviews: keyViews) - keyLabelStackView.axis = .vertical - - let valueLabelStackView = UIStackView(arrangedSubviews: valueViews) - valueLabelStackView.axis = .vertical - - labelStackView = UIStackView(arrangedSubviews: [keyLabelStackView, valueLabelStackView]) - labelStackView.axis = .horizontal - labelStackView.spacing = 8.0 - addSubview(labelStackView) + primaryValueLabel?.font = UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: .light) } private func setupButton() { @@ -137,10 +125,27 @@ class PreviewColorView: UIView { infoButton.addTarget(self, action: #selector(infoButtonPressed(_:)), for: .touchUpInside) } - private func setupConstraints() { + private func setupLayout() { + #warning("This constraint needs to have a lower prio, since it will always break if you are using the picker outside of an action sheet") anchor(heightConstant: 100.0) - labelStackView.anchor(top: topAnchor, paddingTop: 10.0, left: leftAnchor, paddingLeft: 10.0) infoButton.anchor(top: topAnchor, paddingTop: 10.0, right: rightAnchor, paddingRight: 10.0) + + let keysyStackView = UIStackView(arrangedSubviews: [primaryTitleLabel, hexTitleLabel]) + keysyStackView.axis = .vertical + keysyStackView.alignment = .leading + keysyStackView.spacing = 4.0 + + let valuesStackView = UIStackView(arrangedSubviews: [primaryValueLabel, hexValueTextField]) + valuesStackView.axis = .vertical + valuesStackView.alignment = .leading + valuesStackView.spacing = 4.0 + + labelStackView = UIStackView(arrangedSubviews: [keysyStackView, valuesStackView]) + labelStackView.axis = .horizontal + labelStackView.alignment = .top + labelStackView.spacing = 8.0 + addSubview(labelStackView) + labelStackView.anchor(top: topAnchor, paddingTop: 10.0, left: leftAnchor, paddingLeft: 10.0) } private func setupGestureRecognizer() { @@ -148,6 +153,10 @@ class PreviewColorView: UIView { addGestureRecognizer(tap) } + private func setupTextFieldHandler() { + hexValueTextField.delegate = self + } + override func layoutSubviews() { super.layoutSubviews() @@ -161,7 +170,9 @@ class PreviewColorView: UIView { extension PreviewColorView { @objc func handleTap(_: UIView) { if isColorViewLabelShown { + hexValueTextField.unselectTextField() hideLabels() + } else { displayLabels() } @@ -174,6 +185,14 @@ extension PreviewColorView { } } +// MARK: - Handle User Interaction + +extension PreviewColorView: HexTextFieldDelegate { + func hexTextField(_: HexTextField, didEditHexValue value: String) { + delegate?.previewColorView(self, didEditHexValue: value) + } +} + // MARK: - Animations extension PreviewColorView { @@ -193,9 +212,10 @@ extension PreviewColorView { func updateLabelVisibility(withDuration duration: TimeInterval) { UIView.animate(withDuration: duration) { - for label in [self.primaryKeyLabel, self.primaryValueLabel, self.secondaryKeyLabel, self.secondaryValueLabel] { + for label in [self.primaryTitleLabel, self.primaryValueLabel, self.hexTitleLabel] { label?.alpha = self.isColorViewLabelShown ? 1.0 : 0.0 } + self.hexValueTextField?.alpha = self.isColorViewLabelShown ? 1.0 : 0.0 self.infoButton.alpha = self.isColorViewLabelShown ? 0.0 : 1.0 } } diff --git a/SheetyColors/Classes/Common/Views/Protocols/HexTextFieldDelegate.swift b/SheetyColors/Classes/Common/Views/Protocols/HexTextFieldDelegate.swift new file mode 100644 index 0000000..7ef7841 --- /dev/null +++ b/SheetyColors/Classes/Common/Views/Protocols/HexTextFieldDelegate.swift @@ -0,0 +1,12 @@ +// +// HexTextFieldDelegate.swift +// SheetyColors +// +// Created by Wendt, Christoph on 05.04.20. +// + +import Foundation + +protocol HexTextFieldDelegate: AnyObject { + func hexTextField(_ hextTextField: HexTextField, didEditHexValue value: String) +} diff --git a/SheetyColors/Classes/Common/Views/Protocols/PreviewColorViewDelegate.swift b/SheetyColors/Classes/Common/Views/Protocols/PreviewColorViewDelegate.swift new file mode 100644 index 0000000..d84c477 --- /dev/null +++ b/SheetyColors/Classes/Common/Views/Protocols/PreviewColorViewDelegate.swift @@ -0,0 +1,12 @@ +// +// PreviewColorViewDelegate.swift +// SheetyColors +// +// Created by Wendt, Christoph on 04.04.20. +// + +import Foundation + +protocol PreviewColorViewDelegate: AnyObject { + func previewColorView(_ previewColorView: PreviewColorView, didEditHexValue value: String) +} diff --git a/SheetyColors/Classes/Common/Views/SheetyColorsViewController.swift b/SheetyColors/Classes/Common/Views/SheetyColorsViewController.swift index 1d23c1c..a82d189 100644 --- a/SheetyColors/Classes/Common/Views/SheetyColorsViewController.swift +++ b/SheetyColors/Classes/Common/Views/SheetyColorsViewController.swift @@ -12,17 +12,24 @@ import UIKit public class SheetyColorsViewController: UIViewController, SheetyColorsViewControllerProtocol { private var previewColorView: PreviewColorView! private var stackView: UIStackView! - private var selectionFeedback: UISelectionFeedbackGenerator? - var viewModel: SheetyColorsViewModelProtocol! - var hapticFeedbackEnabled: Bool = false + var hapticFeedbackProvider: HapticFeedbackProviderProtocol? + var viewModel: SheetyColorsViewModelProtocol var sliders: [GradientSlider] = [] var previewColor: UIColor { return viewModel.previewColorModel.uiColor } - class func create() -> SheetyColorsViewController { - return SheetyColorsViewController(nibName: "SheetyColorsViewController", bundle: Bundle.framework) + init(viewModel: SheetyColorsViewModelProtocol) { + self.viewModel = viewModel + if viewModel.isHapticFeedbackEnabled { + hapticFeedbackProvider = HapticFeedbackProvider() + } + super.init(nibName: nil, bundle: nil) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") } public override func viewDidLoad() { @@ -66,11 +73,12 @@ extension SheetyColorsViewController { } func setupPreviewColorView() { - previewColorView = PreviewColorView(withColor: viewModel.previewColorModel.uiColor) + previewColorView = PreviewColorView(withColor: viewModel.previewColorModel.uiColor, hapticFeedbackProvider: hapticFeedbackProvider) previewColorView.primaryKeyText = viewModel.primaryKeyText previewColorView.primaryValueText = viewModel.primaryValueText - previewColorView.secondaryKeyText = viewModel.secondaryKeyText - previewColorView.secondaryValueText = viewModel.secondaryValueText + previewColorView.hexKeyText = viewModel.secondaryKeyText + previewColorView.hexValueText = viewModel.secondaryValueText + previewColorView.delegate = self } func setupStackView() { @@ -97,10 +105,6 @@ extension SheetyColorsViewController { extension SheetyColorsViewController { @objc func sliderDidStartEditing(_: GradientSlider) { - if hapticFeedbackEnabled { - selectionFeedback = UISelectionFeedbackGenerator() - } - previewColorView.displayLabels() } @@ -109,14 +113,11 @@ extension SheetyColorsViewController { viewModel.sliderValueChanged(forSliderAt: index, value: sender.value) } - if hapticFeedbackEnabled { - selectionFeedback?.prepare() - selectionFeedback?.selectionChanged() - } + hapticFeedbackProvider?.generateSelectionFeedback() } @objc func sliderDidEndEditing(_: GradientSlider) { - selectionFeedback = nil + hapticFeedbackProvider?.resetSelectionFeedback() previewColorView.hideLabels() } } @@ -124,17 +125,31 @@ extension SheetyColorsViewController { // MARK: - Data binding extension SheetyColorsViewController: SheetyColorsViewDelegate { - func didUpdateColorComponent(in viewModel: SheetyColorsViewModelProtocol) { + func didUpdateColorComponent(in viewModel: SheetyColorsViewModelProtocol, shouldAnimate: Bool) { previewColorView.primaryValueText = viewModel.primaryValueText - previewColorView.secondaryValueText = viewModel.secondaryValueText + previewColorView.hexValueText = viewModel.secondaryValueText + + if !shouldAnimate { + CATransaction.setValue(true, forKey: kCATransactionDisableActions) + } - CATransaction.setValue(true, forKey: kCATransactionDisableActions) previewColorView.color = viewModel.previewColorModel.uiColor for index in 0 ..< viewModel.numberOfSliders { let slider = sliders[index] + slider.value = viewModel.value(forSliderAt: index) slider.minColor = viewModel.minimumColorModel(forSliderAt: index).uiColor slider.maxColor = viewModel.maximumColorModel(forSliderAt: index).uiColor } CATransaction.commit() } } + +// MARK: - PreviewColorViewDelegate + +extension SheetyColorsViewController: PreviewColorViewDelegate { + func previewColorView(_: PreviewColorView, didEditHexValue value: String) { + guard let color = UIColor(hex: value) else { return } + + viewModel.hexValueChanged(withColor: color) + } +} diff --git a/SheetyColors/Classes/Common/Views/SheetyColorsViewController.xib b/SheetyColors/Classes/Common/Views/SheetyColorsViewController.xib deleted file mode 100644 index 8826a17..0000000 --- a/SheetyColors/Classes/Common/Views/SheetyColorsViewController.xib +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SheetyColors/Classes/Grayscale/ViewModels/GrayscaleViewModel.swift b/SheetyColors/Classes/Grayscale/ViewModels/GrayscaleViewModel.swift index 7fa47d7..fef359c 100644 --- a/SheetyColors/Classes/Grayscale/ViewModels/GrayscaleViewModel.swift +++ b/SheetyColors/Classes/Grayscale/ViewModels/GrayscaleViewModel.swift @@ -12,6 +12,7 @@ private enum SliderType: Int, CaseIterable { } class GrayscaleViewModel { + let isHapticFeedbackEnabled: Bool let hasTextOrMessage: Bool let isAlphaEnabled: Bool var colorModel: GrayscaleColor @@ -23,10 +24,11 @@ class GrayscaleViewModel { self.appearenceProvider.current }() - init(withColorModel colorModel: GrayscaleColor, isAlphaEnabled: Bool, hasTextOrMessage: Bool) { - self.colorModel = colorModel - self.hasTextOrMessage = hasTextOrMessage - self.isAlphaEnabled = isAlphaEnabled + init(withConfig config: SheetyColorsConfigProtocol) { + colorModel = config.initialColor.grayscaleColor + hasTextOrMessage = config.title != nil || config.message != nil + isAlphaEnabled = config.alphaEnabled + isHapticFeedbackEnabled = config.hapticFeedbackEnabled } } @@ -135,7 +137,12 @@ extension GrayscaleViewModel: SheetyColorsViewModelProtocol { colorModel.alpha = floor(value) } - viewDelegate?.didUpdateColorComponent(in: self) + viewDelegate?.didUpdateColorComponent(in: self, shouldAnimate: false) delegate?.didSelectColor(colorModel.uiColor) } + + func hexValueChanged(withColor color: UIColor) { + colorModel = color.grayscaleColor + viewDelegate?.didUpdateColorComponent(in: self, shouldAnimate: true) + } } diff --git a/SheetyColors/Classes/HSB/ViewModels/HSBViewModel.swift b/SheetyColors/Classes/HSB/ViewModels/HSBViewModel.swift index 0c9c4fd..a127897 100644 --- a/SheetyColors/Classes/HSB/ViewModels/HSBViewModel.swift +++ b/SheetyColors/Classes/HSB/ViewModels/HSBViewModel.swift @@ -12,6 +12,7 @@ private enum SliderType: Int, CaseIterable { } class HSBViewModel { + let isHapticFeedbackEnabled: Bool let hasTextOrMessage: Bool let isAlphaEnabled: Bool var colorModel: HSBAColor @@ -23,10 +24,11 @@ class HSBViewModel { self.appearenceProvider.current }() - init(withColorModel colorModel: HSBAColor, isAlphaEnabled: Bool, hasTextOrMessage: Bool) { - self.colorModel = colorModel - self.hasTextOrMessage = hasTextOrMessage - self.isAlphaEnabled = isAlphaEnabled + init(withConfig config: SheetyColorsConfigProtocol) { + colorModel = config.initialColor.hsbaColor + hasTextOrMessage = config.title != nil || config.message != nil + isAlphaEnabled = config.alphaEnabled + isHapticFeedbackEnabled = config.hapticFeedbackEnabled } } @@ -163,7 +165,12 @@ extension HSBViewModel: SheetyColorsViewModelProtocol { colorModel.alpha = floor(value) } - viewDelegate?.didUpdateColorComponent(in: self) + viewDelegate?.didUpdateColorComponent(in: self, shouldAnimate: false) delegate?.didSelectColor(colorModel.uiColor) } + + func hexValueChanged(withColor color: UIColor) { + colorModel = color.hsbaColor + viewDelegate?.didUpdateColorComponent(in: self, shouldAnimate: true) + } } diff --git a/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift b/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift index fe7c531..12c7480 100644 --- a/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift +++ b/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift @@ -12,6 +12,7 @@ private enum SliderType: Int, CaseIterable { } class RGBViewModel { + let isHapticFeedbackEnabled: Bool let hasTextOrMessage: Bool let isAlphaEnabled: Bool var colorModel: RGBAColor @@ -23,10 +24,11 @@ class RGBViewModel { self.appearenceProvider.current }() - init(withColorModel colorModel: RGBAColor, isAlphaEnabled: Bool, hasTextOrMessage: Bool) { - self.colorModel = colorModel - self.hasTextOrMessage = hasTextOrMessage - self.isAlphaEnabled = isAlphaEnabled + init(withConfig config: SheetyColorsConfigProtocol) { + colorModel = config.initialColor.rgbaColor + hasTextOrMessage = config.title != nil || config.message != nil + isAlphaEnabled = config.alphaEnabled + isHapticFeedbackEnabled = config.hapticFeedbackEnabled } } @@ -160,7 +162,12 @@ extension RGBViewModel: SheetyColorsViewModelProtocol { colorModel.alpha = floor(value) } - viewDelegate?.didUpdateColorComponent(in: self) + viewDelegate?.didUpdateColorComponent(in: self, shouldAnimate: false) delegate?.didSelectColor(colorModel.uiColor) } + + func hexValueChanged(withColor color: UIColor) { + colorModel = color.rgbaColor + viewDelegate?.didUpdateColorComponent(in: self, shouldAnimate: true) + } }