diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b8991a..21abff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ -## Version 0.1.0 +## Version 0.2.0 +### Features +* HSB support +## Version 0.1.0 ### Features * Initial library setup * RGB support \ No newline at end of file diff --git a/Documentation/Reference/README.md b/Documentation/Reference/README.md index 78c5f45..5891521 100644 --- a/Documentation/Reference/README.md +++ b/Documentation/Reference/README.md @@ -1,6 +1,4 @@ # Reference Documentation -This Reference Documentation has been generated with -[SourceDocs](https://github.com/eneko/SourceDocs). ## Protocols @@ -13,6 +11,7 @@ This Reference Documentation has been generated with ## Classes +- [HSBAColor](classes/HSBAColor.md) - [RGBAColor](classes/RGBAColor.md) ## Enums @@ -21,6 +20,7 @@ This Reference Documentation has been generated with ## Extensions +- [HSBAColor](extensions/HSBAColor.md) - [RGBAColor](extensions/RGBAColor.md) - [SheetyColorsController](extensions/SheetyColorsController.md) - [UIColor](extensions/UIColor.md) diff --git a/Documentation/Reference/classes/HSBAColor.md b/Documentation/Reference/classes/HSBAColor.md new file mode 100644 index 0000000..2d8b9a4 --- /dev/null +++ b/Documentation/Reference/classes/HSBAColor.md @@ -0,0 +1,52 @@ +**CLASS** + +# `HSBAColor` + +```swift +public class HSBAColor: NSObject, NSCopying, Codable +``` + +> A model class representing HSBA colors. The hue component can hold values between 0.0 and 360.0 while the saturation and brightnes values have a maximum value of 100.0. + +## Methods +### `init(hue:saturation:brightness:alpha:)` + +```swift +public init(hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) +``` + +> Creates a HSBAColor instance. +> +> - Parameter: +> - hue: The hue component. +> - saturation: The saturation component. +> - brightness: The brightness component. +> - alpha: The opacity component. + +### `copy(with:)` + +```swift +public func copy(with _: NSZone? = nil) -> Any +``` + +> Creates a copy of the HSBAColor instance. +> +> - Returns: A copy of the HSBAColor instance. + +### `isEqual(_:)` + +```swift +public override func isEqual(_ object: Any?) -> Bool +``` + +> Compares two HSBAColor instances with each other. +> +> - Parameter object: The HSBAColor to compare with. +> +> - Returns: 'true' if the instance is equal to the other HSBAColor instance, otherwise 'false''. + +#### Parameters + +| Name | Description | +| ---- | ----------- | +| object | The HSBAColor to compare with. | \ No newline at end of file diff --git a/Documentation/Reference/enums/SheetyColorsType.md b/Documentation/Reference/enums/SheetyColorsType.md index 84a2254..a32e1b9 100644 --- a/Documentation/Reference/enums/SheetyColorsType.md +++ b/Documentation/Reference/enums/SheetyColorsType.md @@ -16,3 +16,11 @@ case rgb ``` > The RGB color model. + +### `hsb` + +```swift +case hsb +``` + +> The HSB color model. diff --git a/Documentation/Reference/extensions/HSBAColor.md b/Documentation/Reference/extensions/HSBAColor.md new file mode 100644 index 0000000..8503725 --- /dev/null +++ b/Documentation/Reference/extensions/HSBAColor.md @@ -0,0 +1,12 @@ +**EXTENSION** + +# `HSBAColor` + +## Properties +### `uiColor` + +```swift +public var uiColor: UIColor +``` + +> The UIColor representation of the HSBAColor. diff --git a/Documentation/Reference/extensions/UIColor.md b/Documentation/Reference/extensions/UIColor.md index fac5741..7b2ad66 100644 --- a/Documentation/Reference/extensions/UIColor.md +++ b/Documentation/Reference/extensions/UIColor.md @@ -3,6 +3,14 @@ # `UIColor` ## Properties +### `hsbaColor` + +```swift +var hsbaColor: HSBAColor +``` + +> The HSBAColor representation of the UIColor instance. + ### `rgbaColor` ```swift diff --git a/Documentation/sheetycolors.gif b/Documentation/sheetycolors.gif new file mode 100644 index 0000000..12907c4 Binary files /dev/null and b/Documentation/sheetycolors.gif differ diff --git a/Documentation/sheetycolors_demo.gif b/Documentation/sheetycolors_demo.gif deleted file mode 100644 index 362f602..0000000 Binary files a/Documentation/sheetycolors_demo.gif and /dev/null differ diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 46fa7da..6ad1cff 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -2,7 +2,7 @@ PODS: - Capable/Colors (0.9.0) - Nimble (8.0.1) - Quick (2.0.0) - - SheetyColors (0.1.0): + - SheetyColors (0.2.0): - Capable/Colors (~> 0.9.0) - SnapshotTesting (1.5.0) @@ -27,7 +27,7 @@ SPEC CHECKSUMS: Capable: 3f2cbbd61326df7304ff97ca4b954c2de2e3ff43 Nimble: 45f786ae66faa9a709624227fae502db55a8bdd0 Quick: ce1276c7c27ba2da3cb2fd0cde053c3648b3b22d - SheetyColors: 256f907e54648c5d08b52bdacaef6b795242ad59 + SheetyColors: 483d95d270f01dde725b68217b63704f1efc984b SnapshotTesting: 9ca1d80f6322509a035856170c11d0571b1f7171 PODFILE CHECKSUM: 5b2abbedbad9c9292a41e99328101b079df5f152 diff --git a/Example/SheetyColors.xcodeproj/project.pbxproj b/Example/SheetyColors.xcodeproj/project.pbxproj index 56ba170..7649bce 100644 --- a/Example/SheetyColors.xcodeproj/project.pbxproj +++ b/Example/SheetyColors.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 831FCF0022591DFE003C48A4 /* UIAlertController+customViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 831FCEFF22591DFE003C48A4 /* UIAlertController+customViewTests.swift */; }; 831FCF02225E62F8003C48A4 /* SheetyColorsViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 831FCF01225E62F8003C48A4 /* SheetyColorsViewTests.swift */; }; 83205EE52256815500D62BB8 /* UIView+findLabelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83205EE42256815500D62BB8 /* UIView+findLabelTests.swift */; }; + 832DCEFF226E4D7C00DAF3EF /* HSBViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832DCEFE226E4D7B00DAF3EF /* HSBViewModelTests.swift */; }; + 832DCF03226F92E100DAF3EF /* UIColor+hsbaColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832DCF02226F92E100DAF3EF /* UIColor+hsbaColorTests.swift */; }; 834A36DC224686C500DECD37 /* RGBAColors+NSItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834A36DB224686C500DECD37 /* RGBAColors+NSItemProvider.swift */; }; 834A36E022496F8A00DECD37 /* SheetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834A36DF22496F8A00DECD37 /* SheetType.swift */; }; 834A36E2224971F700DECD37 /* SheetyColorsViewFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834A36E1224971F700DECD37 /* SheetyColorsViewFactoryTests.swift */; }; @@ -64,6 +66,8 @@ 831FCEFF22591DFE003C48A4 /* UIAlertController+customViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+customViewTests.swift"; sourceTree = ""; }; 831FCF01225E62F8003C48A4 /* SheetyColorsViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetyColorsViewTests.swift; sourceTree = ""; }; 83205EE42256815500D62BB8 /* UIView+findLabelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+findLabelTests.swift"; sourceTree = ""; }; + 832DCEFE226E4D7B00DAF3EF /* HSBViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HSBViewModelTests.swift; sourceTree = ""; }; + 832DCF02226F92E100DAF3EF /* UIColor+hsbaColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+hsbaColorTests.swift"; sourceTree = ""; }; 834A36DB224686C500DECD37 /* RGBAColors+NSItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RGBAColors+NSItemProvider.swift"; sourceTree = ""; }; 834A36DF22496F8A00DECD37 /* SheetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetType.swift; sourceTree = ""; }; 834A36E1224971F700DECD37 /* SheetyColorsViewFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetyColorsViewFactoryTests.swift; sourceTree = ""; }; @@ -176,6 +180,7 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( + 832DCEFC226E4D5900DAF3EF /* HSB */, 834A36EA224E893B00DECD37 /* Common */, 834A36EB224E894900DECD37 /* RGB */, 834A36E32249823800DECD37 /* Mocks */, @@ -221,6 +226,39 @@ name = Extensions; sourceTree = ""; }; + 832DCEFC226E4D5900DAF3EF /* HSB */ = { + isa = PBXGroup; + children = ( + 832DCF00226F92BE00DAF3EF /* Models */, + 832DCEFD226E4D6200DAF3EF /* ViewModels */, + ); + name = HSB; + sourceTree = ""; + }; + 832DCEFD226E4D6200DAF3EF /* ViewModels */ = { + isa = PBXGroup; + children = ( + 832DCEFE226E4D7B00DAF3EF /* HSBViewModelTests.swift */, + ); + name = ViewModels; + sourceTree = ""; + }; + 832DCF00226F92BE00DAF3EF /* Models */ = { + isa = PBXGroup; + children = ( + 832DCF01226F92C600DAF3EF /* Extensions */, + ); + name = Models; + sourceTree = ""; + }; + 832DCF01226F92C600DAF3EF /* Extensions */ = { + isa = PBXGroup; + children = ( + 832DCF02226F92E100DAF3EF /* UIColor+hsbaColorTests.swift */, + ); + name = Extensions; + sourceTree = ""; + }; 834A36E32249823800DECD37 /* Mocks */ = { isa = PBXGroup; children = ( @@ -557,6 +595,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 832DCEFF226E4D7C00DAF3EF /* HSBViewModelTests.swift in Sources */, + 832DCF03226F92E100DAF3EF /* UIColor+hsbaColorTests.swift in Sources */, 834A36E52249829300DECD37 /* SheetyColorsConfigMock.swift in Sources */, 83DDE2212264FD1C005F4CEF /* SheetyColorsConfigTests.swift in Sources */, 83DDE21A225FBCBC005F4CEF /* UIColor+rgbaColorTests.swift in Sources */, diff --git a/Example/SheetyColors/Base.lproj/Main.storyboard b/Example/SheetyColors/Base.lproj/Main.storyboard index b9846fc..987ed21 100644 --- a/Example/SheetyColors/Base.lproj/Main.storyboard +++ b/Example/SheetyColors/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -59,7 +59,7 @@ - + diff --git a/Example/SheetyColors/ColorsCollectionViewController.swift b/Example/SheetyColors/ColorsCollectionViewController.swift index 1305c52..5757861 100644 --- a/Example/SheetyColors/ColorsCollectionViewController.swift +++ b/Example/SheetyColors/ColorsCollectionViewController.swift @@ -40,25 +40,38 @@ class ColorsCollectionViewController: UICollectionViewController { collectionView.dragInteractionEnabled = true } - @IBAction func openSheetyColors(_: Any) { - openColorsActionSheet(with: .createColor) + @IBAction func addColor(_: Any) { + openColorTypeSelection(forAction: .createColor) } - func openEditActionSheet(forItemAt item: Int) { - openColorsActionSheet(with: .editColor(item: item)) + func editColor(forItemAt item: Int) { + openColorTypeSelection(forAction: .editColor(item: item)) } - func openColorsActionSheet(with type: SheetType) { + func openColorTypeSelection(forAction action: SheetType) { + let colorTypeSelectionSheet = UIAlertController(title: "Color Type", message: "Select a color type to use for your color picker.", preferredStyle: .actionSheet) + colorTypeSelectionSheet.addAction(UIAlertAction(title: "HSB", style: .default, handler: { _ in + self.openColorPicker(withAction: action, colorType: .hsb) + })) + colorTypeSelectionSheet.addAction(UIAlertAction(title: "RGB", style: .default, handler: { _ in + self.openColorPicker(withAction: action, colorType: .rgb) + })) + addCancelAlertAction(for: colorTypeSelectionSheet) + + present(colorTypeSelectionSheet, animated: true) + } + + func openColorPicker(withAction action: SheetType, colorType: SheetyColorsType) { var itemIndex: Int? - if case let .editColor(item) = type { + if case let .editColor(item) = action { itemIndex = item } let color = (itemIndex != nil) ? colorItems[itemIndex!].uiColor : UIColor.white - let config = SheetyColorsConfig(alphaEnabled: true, hapticFeedbackEnabled: true, initialColor: color, title: type.title, type: .rgb) + let config = SheetyColorsConfig(alphaEnabled: true, hapticFeedbackEnabled: true, initialColor: color, title: action.title, type: colorType) let controller = SheetyColorsController(withConfig: config) - addSelectAlertAction(for: controller, withType: type) + addSelectAlertAction(for: controller, withAction: action) addCancelAlertAction(for: controller) if let itemIndex = itemIndex { addDeleteAlertAction(for: controller, item: itemIndex) @@ -67,10 +80,10 @@ class ColorsCollectionViewController: UICollectionViewController { present(controller, animated: true, completion: nil) } - func addSelectAlertAction(for controller: SheetyColorsController, withType type: SheetType) { + func addSelectAlertAction(for controller: SheetyColorsController, withAction action: SheetType) { let selectAction = UIAlertAction(title: "Save Color", style: .default, handler: { _ in self.collectionView.performBatchUpdates({ - if case let .editColor(item) = type { + if case let .editColor(item) = action { self.colorItems[item] = controller.color.rgbaColor self.collectionView.reloadItems(at: [IndexPath(item: item, section: 0)]) } else { @@ -99,7 +112,7 @@ class ColorsCollectionViewController: UICollectionViewController { controller.addAction(deleteAction) } - func addCancelAlertAction(for controller: SheetyColorsController) { + func addCancelAlertAction(for controller: UIAlertController) { let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) controller.addAction(cancelAction) } @@ -134,7 +147,7 @@ extension ColorsCollectionViewController { extension ColorsCollectionViewController { override func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) { - openEditActionSheet(forItemAt: indexPath.item) + editColor(forItemAt: indexPath.item) } } diff --git a/Example/Tests/HSBViewModelTests.swift b/Example/Tests/HSBViewModelTests.swift new file mode 100644 index 0000000..ddbe2f8 --- /dev/null +++ b/Example/Tests/HSBViewModelTests.swift @@ -0,0 +1,358 @@ +// +// HSBViewModelTests.swift +// SheetyColors_Tests +// +// Created by Christoph Wendt on 22.04.19. +// Copyright © 2019 CocoaPods. All rights reserved. +// + +import Nimble +import Quick +@testable import SheetyColors + +class HSBViewModelTests: QuickSpec { + override func spec() { + describe("The HSBViewModel") { + var sut: HSBViewModel! + var delegateMock: SheetyColorsViewModelDelegateMock! + + context("after initialization") { + var testColorModel: HSBAColor! + var testIsAlphaEnabled: Bool! + + beforeEach { + delegateMock = SheetyColorsViewModelDelegateMock() + testIsAlphaEnabled = true + testColorModel = HSBAColor(hue: 123.0, saturation: 94.0, brightness: 87.0, alpha: 69.0) + sut = HSBViewModel(withColorModel: testColorModel, alphaEnabled: testIsAlphaEnabled) + sut.viewModelDelegate = delegateMock + } + + it("sets up the instance correctly") { + expect(sut).to(beAnInstanceOf(HSBViewModel.self)) + } + + context("when calling primaryKeyText property") { + it("returns string HSB") { + expect(sut!.primaryKeyText).to(equal("HSB")) + } + } + + context("when calling primaryValueText property") { + it("returns string representation of HSBA values") { + let expectedText = "\(Int(testColorModel.hue)) \(Int(testColorModel.saturation)) \(Int(testColorModel.brightness)) \(Int(testColorModel.alpha))%" + + expect(sut!.primaryValueText).to(equal(expectedText)) + } + } + + context("when calling secondaryKeyText property") { + it("returns string HEX") { + expect(sut!.secondaryKeyText).to(equal("HEX")) + } + } + + context("when calling primaryValueText property") { + it("returns string representation of HEX values") { + expect(sut!.secondaryValueText).to(equal(testColorModel.hexColor)) + } + } + + context("when calling previewColorModel property") { + it("returns the current color model") { + expect(sut!.previewColorModel as? HSBAColor).to(equal(testColorModel)) + } + } + + context("when calling numberOfSliders property") { + it("returns 4") { + expect(sut.numberOfSliders).to(equal(4)) + } + } + + context("when calling stepInterval()") { + context("with index 0") { + it("returns 1.0") { + expect(sut.stepInterval(forSliderAt: 0)).to(equal(1.0)) + } + } + + context("with index 1") { + it("returns 1.0") { + expect(sut.stepInterval(forSliderAt: 1)).to(equal(1.0)) + } + } + + context("with index 2") { + it("returns 1.0") { + expect(sut.stepInterval(forSliderAt: 2)).to(equal(1.0)) + } + } + + context("with index 3") { + it("returns 1.0") { + expect(sut.stepInterval(forSliderAt: 3)).to(equal(1.0)) + } + } + } + + context("when calling value()") { + context("with index 0") { + it("returns the color model's hue component value") { + expect(sut.value(forSliderAt: 0)).to(equal(testColorModel.hue)) + } + } + + context("with index 1") { + it("returns the color model's saturation component value") { + expect(sut.value(forSliderAt: 1)).to(equal(testColorModel.saturation)) + } + } + + context("with index 2") { + it("returns the color model's brightness component value") { + expect(sut.value(forSliderAt: 2)).to(equal(testColorModel.brightness)) + } + } + + context("with index 3") { + it("returns the color model's alpha component value") { + expect(sut.value(forSliderAt: 3)).to(equal(testColorModel.alpha)) + } + } + } + + context("when calling maximumValue()") { + context("with index 0") { + it("returns 360.0") { + expect(sut.maximumValue(forSliderAt: 0)).to(equal(360.0)) + } + } + + context("with index 1") { + it("returns 100.0") { + expect(sut.maximumValue(forSliderAt: 1)).to(equal(100.0)) + } + } + + context("with index 2") { + it("returns 100.0") { + expect(sut.maximumValue(forSliderAt: 2)).to(equal(100.0)) + } + } + + context("with index 3") { + it("returns 100.0") { + expect(sut.maximumValue(forSliderAt: 3)).to(equal(100.0)) + } + } + } + + context("when calling minimumColorModel()") { + context("with index 0") { + it("returns correct min color model with hue component of 0.0 and full opacity") { + let actual = sut.minimumColorModel(forSliderAt: 0) as! HSBAColor + let expected = testColorModel.copy() as! HSBAColor + expected.hue = 0.0 + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + + context("with index 1") { + it("returns correct min color model with saturation component of 0.0 and full opacity") { + let actual = sut.minimumColorModel(forSliderAt: 1) as! HSBAColor + let expected = testColorModel.copy() as! HSBAColor + expected.saturation = 0.0 + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + + context("with index 2") { + it("returns correct min color model with brightness component of 0.0 and full opacity") { + let actual = sut.minimumColorModel(forSliderAt: 2) as! HSBAColor + let expected = testColorModel.copy() as! HSBAColor + expected.brightness = 0.0 + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + + context("with index 3") { + it("returns color model representing white color") { + let actual = sut.minimumColorModel(forSliderAt: 3) as! HSBAColor + let expected = HSBAColor(hue: 360.0, saturation: 0.0, brightness: 100.0, alpha: 100.0) + + expect(actual).to(equal(expected)) + } + } + } + + context("when calling maximumColorModel()") { + context("with index 0") { + it("returns correct max color model with hue component of 360.0 and full opacity") { + let actual = sut.maximumColorModel(forSliderAt: 0) as! HSBAColor + let expected = testColorModel.copy() as! HSBAColor + expected.hue = 360.0 + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + + context("with index 1") { + it("returns correct max color model with saturation component of 100.0 and full opacity") { + let actual = sut.maximumColorModel(forSliderAt: 1) as! HSBAColor + let expected = testColorModel.copy() as! HSBAColor + expected.saturation = 100.0 + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + + context("with index 2") { + it("returns correct max color model with brightness component of 100.0 and full opacity") { + let actual = sut.maximumColorModel(forSliderAt: 2) as! HSBAColor + let expected = testColorModel.copy() as! HSBAColor + expected.brightness = 100.0 + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + + context("with index 3") { + it("returns correct max color model with alpha component of 100.0") { + let actual = sut.maximumColorModel(forSliderAt: 3) as! HSBAColor + let expected = testColorModel.copy() as! HSBAColor + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + } + + context("when calling thumbText()") { + context("with index 0") { + it("returns H") { + expect(sut.thumbText(forSliderAt: 0)).to(equal("H")) + } + } + + context("with index 1") { + it("returns S") { + expect(sut.thumbText(forSliderAt: 1)).to(equal("S")) + } + } + + context("with index 2") { + it("returns B") { + expect(sut.thumbText(forSliderAt: 2)).to(equal("B")) + } + } + + context("with index 3") { + it("returns %") { + expect(sut.thumbText(forSliderAt: 3)).to(equal("%")) + } + } + } + + context("when calling thumbIconName()") { + context("with index 0") { + it("returns nil") { + expect(sut.thumbIconName(forSliderAt: 0)).to(beNil()) + } + } + + context("with index 1") { + it("returns nil") { + expect(sut.thumbIconName(forSliderAt: 1)).to(beNil()) + } + } + + context("with index 2") { + it("returns nil") { + expect(sut.thumbIconName(forSliderAt: 2)).to(beNil()) + } + } + + context("with index 3") { + it("returns nil") { + expect(sut.thumbIconName(forSliderAt: 3)).to(beNil()) + } + } + } + + context("when calling sliderValueChanged()") { + var testColorValue: CGFloat! + + beforeEach { + testColorValue = 99.9 + } + + context("with index 0") { + beforeEach { + sut.sliderValueChanged(forSliderAt: 0, value: testColorValue) + } + + it("informs the delegate") { + expect(delegateMock.didCallDidUpdateColorComponent).to(beTrue()) + } + + it("floors the value and updates the hue component of the color model") { + expect(sut.colorModel.hue).to(equal(floor(testColorValue))) + } + } + + context("with index 1") { + beforeEach { + sut.sliderValueChanged(forSliderAt: 1, value: testColorValue) + } + + it("informs the delegate") { + expect(delegateMock.didCallDidUpdateColorComponent).to(beTrue()) + } + + it("floors the value and updates the saturation component of the color model") { + expect(sut.colorModel.saturation).to(equal(floor(testColorValue))) + } + } + + context("with index 2") { + beforeEach { + sut.sliderValueChanged(forSliderAt: 2, value: testColorValue) + } + + it("informs the delegate") { + expect(delegateMock.didCallDidUpdateColorComponent).to(beTrue()) + } + + it("floors the value and updates the brightness component of the color model") { + expect(sut.colorModel.brightness).to(equal(floor(testColorValue))) + } + } + + context("with index 3") { + beforeEach { + sut.sliderValueChanged(forSliderAt: 3, value: testColorValue) + } + + it("informs the delegate") { + expect(delegateMock.didCallDidUpdateColorComponent).to(beTrue()) + } + + it("floors the value and updates the alpha component of the color model") { + expect(sut.colorModel.alpha).to(equal(floor(testColorValue))) + } + } + } + } + } + } +} diff --git a/Example/Tests/SheetyColorsViewModelMock.swift b/Example/Tests/SheetyColorsViewModelMock.swift index c8aeb49..7111106 100644 --- a/Example/Tests/SheetyColorsViewModelMock.swift +++ b/Example/Tests/SheetyColorsViewModelMock.swift @@ -18,6 +18,10 @@ class SheetyColorsViewModelMock: SheetyColorsViewModelProtocol { var previewColorModel: SheetyColorProtocol = RGBAColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) var numberOfSliders: Int = 0 + func rainbowEnabled(forSliderAt _: Int) -> Bool { + return false + } + func stepInterval(forSliderAt _: Int) -> CGFloat { return 0.0 } diff --git a/Example/Tests/SheetyColorsViewTests.swift b/Example/Tests/SheetyColorsViewTests.swift index fae3b9a..cf6cd24 100644 --- a/Example/Tests/SheetyColorsViewTests.swift +++ b/Example/Tests/SheetyColorsViewTests.swift @@ -16,15 +16,14 @@ class SheetyColorsViewTests: QuickSpec { describe("The SheetyColorsView") { var sut: SheetyColorsView! var testSlider: GradientSlider! - var testColor: RGBAColor! beforeEach { testSlider = GradientSlider(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)) - testColor = UIColor(red: 0.0, green: 0.25, blue: 0.5, alpha: 0.75).rgbaColor } - 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, alphaEnabled: false) sut = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: false) viewModel.viewModelDelegate = sut @@ -71,6 +70,7 @@ class SheetyColorsViewTests: QuickSpec { 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, alphaEnabled: true) sut = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: false) viewModel.viewModelDelegate = sut @@ -82,6 +82,36 @@ class SheetyColorsViewTests: QuickSpec { assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) } } + + 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, alphaEnabled: false) + sut = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: false) + viewModel.viewModelDelegate = 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))) + } + } + + 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, alphaEnabled: true) + sut = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: false) + viewModel.viewModelDelegate = 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))) + } + } } } } diff --git a/Example/Tests/UIColor+hsbaColorTests.swift b/Example/Tests/UIColor+hsbaColorTests.swift new file mode 100644 index 0000000..bcb28fe --- /dev/null +++ b/Example/Tests/UIColor+hsbaColorTests.swift @@ -0,0 +1,69 @@ +// +// UIColor+hsbaColorTests.swift +// SheetyColors_Tests +// +// Created by Christoph Wendt on 23.04.19. +// Copyright © 2019 CocoaPods. All rights reserved. +// + +import Nimble +import Quick +@testable import SheetyColors + +class UIColorHsbaColorTests: QuickSpec { + override func spec() { + describe("The UIColor+hsbaColor extension") { + var sut: HSBAColor! + + context("when calling hsbaColor on a hsb colorspace color") { + beforeEach { + sut = UIColor(hue: 0.1, saturation: 0.25, brightness: 0.75, alpha: 0.5).hsbaColor + } + + it("returns an HSBA color model") { + expect(sut.hue).to(equal(36.0)) + expect(sut.saturation).to(equal(25.0)) + expect(sut.brightness).to(equal(75.0)) + expect(sut.alpha).to(equal(50.0)) + } + } + + context("when calling hsbaColor on a gray colorspace color") { + beforeEach { + sut = UIColor(white: 1.0, alpha: 0.5).hsbaColor + } + + it("returns an HSBA color model") { + expect(sut.saturation).to(equal(0.0)) + expect(sut.brightness).to(equal(100.0)) + } + } + + context("when calling hsbaColor on a rgb colorspace color") { + beforeEach { + sut = UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5).hsbaColor + } + + it("returns an HSBA color model") { + expect(sut.hue).to(equal(300.0)) + expect(sut.saturation).to(equal(100.0)) + expect(sut.brightness).to(equal(100.0)) + expect(sut.alpha).to(equal(50.0)) + } + } + + context("when calling hsbaColor on a rgb extended colorspace color") { + beforeEach { + sut = UIColor(red: 2.0, green: -1.0, blue: 1.0, alpha: 2.0).hsbaColor + } + + it("returns an HSBA color model") { + expect(sut.hue).to(equal(320.0)) + expect(sut.saturation).to(equal(100.0)) + expect(sut.brightness).to(equal(100.0)) + expect(sut.alpha).to(equal(100.0)) + } + } + } + } +} diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.10.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.10.txt new file mode 100644 index 0000000..8483aaf --- /dev/null +++ b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.10.txt @@ -0,0 +1,29 @@ +> + | > + | | ; 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.11.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.11.png new file mode 100644 index 0000000..12b4d8e Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.11.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.12.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.12.png new file mode 100644 index 0000000..b395d8a Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.12.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.13.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.13.txt new file mode 100644 index 0000000..314b5c1 --- /dev/null +++ b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.13.txt @@ -0,0 +1,34 @@ +> + | > + | | ; 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.14.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.14.png new file mode 100644 index 0000000..c9e1f7f Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.14.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.15.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.15.png new file mode 100644 index 0000000..1cb2b75 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.15.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.2.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.2.png index dc04f61..1ff45a6 100644 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.2.png and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.2.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.3.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.3.png index 1afbd08..7038fa0 100644 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.3.png and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.3.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.4.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.4.png index 02e73e3..6a7657f 100644 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.4.png and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.4.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.5.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.5.png index 8f60193..ef5bd19 100644 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.5.png and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.5.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.6.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.6.png index 02e73e3..6a7657f 100644 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.6.png and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.6.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.8.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.8.png index 4df3b48..faed429 100644 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.8.png and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.8.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.9.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.9.png index 558ce93..0d6c194 100644 Binary files a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.9.png and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.9.png differ diff --git a/README.md b/README.md index dc773d3..09e9695 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ +![Cocoapods platforms](https://img.shields.io/cocoapods/p/SheetyColors.svg) +![Cocoapods](https://img.shields.io/cocoapods/v/SheetyColors.svg) +[![Carthage compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage) +[![Build Status](https://app.bitrise.io/app/e955e72e7da4b8c0/status.svg?token=wOm6zBpCFw7ZeP8gJdDE_A&branch=develop)](https://app.bitrise.io/app/e955e72e7da4b8c0) +[![Twitter](https://img.shields.io/badge/twitter-%40chr__wendt-58a1f2.svg)](https://twitter.com/chr_wendt) +

-[![Build Status](https://app.bitrise.io/app/e955e72e7da4b8c0/status.svg?token=wOm6zBpCFw7ZeP8gJdDE_A&branch=develop)](https://app.bitrise.io/app/e955e72e7da4b8c0) - -The **SheetyColors** color picker removes the lack of a built-in iOS component: +**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. -- **Fully configurable:** You can choose between a variety of configurations such as a color model, alpha component support, haptic feedback, and many more. +- **Fully configurable:** You can choose between a variety of configurations such as a color model (RGB or HSB), alpha component support, haptic feedback, 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.

@@ -75,6 +79,8 @@ present(sheetyColors, animated: true, completion: nil) ``` +Please check the [documentation](./Documentation/Reference/README.MD) for further information on the API. + ## Contributions We'd love to see you contributing to this project by proposing or adding features, reporting bugs, or spreading the word. Please have a quick look at our [contribution guidelines](./.github/CONTRIBUTING.md). diff --git a/Scripts/release.sh b/Scripts/release.sh new file mode 100644 index 0000000..588f594 --- /dev/null +++ b/Scripts/release.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Run this script from the root directory just before releasing a new SheetyColors version or submitting a new pull request. + +#============================= Linting & Formatting ============================= +echo "Running SwiftFormat" +swift run swiftformat . + +echo "Running SwiftLint" +swift run swiftlint autocorrect --path SheetyColors/ + +#================================= Documentation ================================ +echo "Generating docs with SourceDocs" +swift run sourcedocs generate -- -workspace Example/SheetyColors.xcworkspace -scheme SheetyColors \ No newline at end of file diff --git a/Scripts/setup.sh b/Scripts/setup.sh new file mode 100644 index 0000000..d4482dc --- /dev/null +++ b/Scripts/setup.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Run this script from the root directory just before starting to contribute code. + +#================================= Tooling ================================= +echo "Installing tools (SwiftLint, SwiftFormat, and SourceDocs" + +swift build \ No newline at end of file diff --git a/SheetyColors.podspec b/SheetyColors.podspec index 67f0454..047bedc 100644 --- a/SheetyColors.podspec +++ b/SheetyColors.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SheetyColors' - s.version = '0.1.0' + s.version = '0.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 365ae1c..7839283 100644 --- a/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift +++ b/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift @@ -14,6 +14,8 @@ struct SheetyColorsViewFactory { switch config.type { case .rgb: viewModel = RGBViewModel(withColorModel: config.initialColor.rgbaColor, alphaEnabled: config.alphaEnabled) + case .hsb: + viewModel = HSBViewModel(withColorModel: config.initialColor.hsbaColor, alphaEnabled: config.alphaEnabled) } let view = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: config.hapticFeedbackEnabled) diff --git a/SheetyColors/Classes/Common/Models/SheetyColorsType.swift b/SheetyColors/Classes/Common/Models/SheetyColorsType.swift index c9c6061..1d26d8f 100644 --- a/SheetyColors/Classes/Common/Models/SheetyColorsType.swift +++ b/SheetyColors/Classes/Common/Models/SheetyColorsType.swift @@ -9,4 +9,7 @@ public enum SheetyColorsType: Equatable, CaseIterable { /// The RGB color model. case rgb + + /// The HSB color model. + case hsb } diff --git a/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewModelProtocol.swift b/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewModelProtocol.swift index 0fc07f7..6a08e59 100644 --- a/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewModelProtocol.swift +++ b/SheetyColors/Classes/Common/ViewModels/SheetyColorsViewModelProtocol.swift @@ -14,6 +14,7 @@ protocol SheetyColorsViewModelProtocol { var previewColorModel: SheetyColorProtocol { get } var numberOfSliders: Int { get } + func rainbowEnabled(forSliderAt index: Int) -> Bool func stepInterval(forSliderAt index: Int) -> CGFloat func value(forSliderAt index: Int) -> CGFloat func maximumValue(forSliderAt index: Int) -> CGFloat diff --git a/SheetyColors/Classes/Common/Views/SheetyColorsView.swift b/SheetyColors/Classes/Common/Views/SheetyColorsView.swift index bfa3d1d..0c281f6 100644 --- a/SheetyColors/Classes/Common/Views/SheetyColorsView.swift +++ b/SheetyColors/Classes/Common/Views/SheetyColorsView.swift @@ -46,6 +46,7 @@ extension SheetyColorsView { func setupSliders() { for index in 0 ..< viewModel.numberOfSliders { let slider = GradientSlider() + slider.hasRainbow = viewModel.rainbowEnabled(forSliderAt: index) slider.stepInterval = viewModel.stepInterval(forSliderAt: index) slider.maximumValue = viewModel.maximumValue(forSliderAt: index) slider.value = viewModel.value(forSliderAt: index) diff --git a/SheetyColors/Classes/HSB/Models/Extensions/UIColor+hsbaColor.swift b/SheetyColors/Classes/HSB/Models/Extensions/UIColor+hsbaColor.swift new file mode 100644 index 0000000..cf1d42a --- /dev/null +++ b/SheetyColors/Classes/HSB/Models/Extensions/UIColor+hsbaColor.swift @@ -0,0 +1,35 @@ +// +// UIColor+hsbaColor.swift +// SheetyColors +// +// Created by Christoph Wendt on 20.04.19. +// + +import UIKit + +/// Extends UIColor with functionality to convert an instance to a HSBAColor. +public extension UIColor { + /// The HSBAColor representation of the UIColor instance. + var hsbaColor: HSBAColor { + func normalize(_ component: CGFloat, multiplier: CGFloat) -> CGFloat { + let nomralizedValue = floor(component * multiplier) + + if nomralizedValue > multiplier { return multiplier } + if nomralizedValue < 0.0 { return 0.0 } + return nomralizedValue + } + + var hue: CGFloat = 0, saturation: CGFloat = 0, brightness: CGFloat = 0, alpha: CGFloat = 0 + + if getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) { + let hue = normalize(hue, multiplier: 360.0) + let saturation = normalize(saturation, multiplier: 100.0) + let brightness = normalize(brightness, multiplier: 100.0) + let alpha = normalize(alpha, multiplier: 100.0) + + return HSBAColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) + } else { + return HSBAColor(hue: 0.0, saturation: 100.0, brightness: 100.0, alpha: 1) + } + } +} diff --git a/SheetyColors/Classes/HSB/Models/HSBAColor.swift b/SheetyColors/Classes/HSB/Models/HSBAColor.swift new file mode 100644 index 0000000..280e8e9 --- /dev/null +++ b/SheetyColors/Classes/HSB/Models/HSBAColor.swift @@ -0,0 +1,80 @@ +// +// HSBAColor.swift +// SheetyColors +// +// Created by Christoph Wendt on 20.04.19. +// + +import CoreGraphics + +/// A model class representing HSBA colors. The hue component can hold values between 0.0 and 360.0 while the saturation and brightnes values have a maximum value of 100.0. +public class HSBAColor: NSObject, NSCopying, Codable { + var hue, saturation, brightness, alpha: CGFloat + + var hexColor: String { + if let colorRef = self.uiColor.cgColor.components { + let red: CGFloat = colorRef[0] + let green: CGFloat = colorRef[1] + let blue: CGFloat = colorRef[2] + let rgb: Int = Int(red * 255.0) << 16 | Int(green * 255.0) << 8 | Int(blue * 255.0) << 0 + + return String(format: "%06x", rgb).uppercased() + } + + return "" + } + + /** + Creates a HSBAColor instance. + + - Parameter: + - hue: The hue component. + - saturation: The saturation component. + - brightness: The brightness component. + - alpha: The opacity component. + */ + public init(hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) { + self.hue = hue + self.saturation = saturation + self.brightness = brightness + self.alpha = alpha + } + + /** + Creates a copy of the HSBAColor instance. + + - Returns: A copy of the HSBAColor instance. + */ + public func copy(with _: NSZone? = nil) -> Any { + let copy = HSBAColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) + return copy + } + + /** + Compares two HSBAColor instances with each other. + + - Parameter object: The HSBAColor to compare with. + + - Returns: 'true' if the instance is equal to the other HSBAColor instance, otherwise 'false''. + */ + public override func isEqual(_ object: Any?) -> Bool { + guard let otherColor = object as? HSBAColor else { + return false + } + + return hue == otherColor.hue && + saturation == otherColor.saturation && + brightness == otherColor.brightness && + alpha == otherColor.alpha + } +} + +// MARK: - Converting to other color models + +/// An extension adding functionality defined in SheetyColorProtocol to HSBAColor. +extension HSBAColor: SheetyColorProtocol { + /// The UIColor representation of the HSBAColor. + public var uiColor: UIColor { + return UIColor(hue: hue / 360.0, saturation: saturation / 100.0, brightness: brightness / 100.0, alpha: alpha / 100.0) + } +} diff --git a/SheetyColors/Classes/HSB/ViewModels/HSBViewModel.swift b/SheetyColors/Classes/HSB/ViewModels/HSBViewModel.swift new file mode 100644 index 0000000..ca2ccc1 --- /dev/null +++ b/SheetyColors/Classes/HSB/ViewModels/HSBViewModel.swift @@ -0,0 +1,157 @@ +// +// HSBViewModel.swift +// SheetyColors +// +// Created by Christoph Wendt on 20.04.19. +// + +private enum SliderType: Int, CaseIterable { + case hue, saturation, brightness, alpha +} + +class HSBViewModel { + var isAlphaEnabled: Bool + var colorModel: HSBAColor + weak var viewModelDelegate: SheetyColorsViewModelDelegate? + + init(withColorModel colorModel: HSBAColor, alphaEnabled: Bool) { + self.colorModel = colorModel + isAlphaEnabled = alphaEnabled + } +} + +extension HSBViewModel: SheetyColorsViewModelProtocol { + var primaryKeyText: String { + return "HSB" + } + + var primaryValueText: String { + return "\(Int(colorModel.hue)) \(Int(colorModel.saturation)) \(Int(colorModel.brightness)) \(Int(colorModel.alpha))%" + } + + var secondaryKeyText: String { + return "HEX" + } + + var secondaryValueText: String { + return colorModel.hexColor + } + + var previewColorModel: SheetyColorProtocol { + return colorModel + } + + var numberOfSliders: Int { + let maxSliderCount = SliderType.allCases.count + return isAlphaEnabled ? maxSliderCount : maxSliderCount - 1 + } + + func rainbowEnabled(forSliderAt index: Int) -> Bool { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + return slider == .hue + } + + func stepInterval(forSliderAt _: Int) -> CGFloat { + return 1.0 + } + + func value(forSliderAt index: Int) -> CGFloat { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + switch slider { + case .hue: + return colorModel.hue + case .saturation: + return colorModel.saturation + case .brightness: + return colorModel.brightness + case .alpha: + return colorModel.alpha + } + } + + func maximumValue(forSliderAt index: Int) -> CGFloat { + guard let slider = SliderType(rawValue: index) else { fatalError() } + let maxValue: CGFloat = (slider == .hue) ? 360.0 : 100.0 + + return maxValue + } + + func minimumColorModel(forSliderAt index: Int) -> SheetyColorProtocol { + guard let slider = SliderType(rawValue: index) else { fatalError() } + if case .alpha = slider { + return HSBAColor(hue: 360.0, saturation: 0.0, brightness: 100.0, alpha: 100.0) + } + + guard let color = colorModel.copy() as? HSBAColor else { fatalError() } + + switch slider { + case .hue: + color.hue = 0.0 + case .saturation: + color.saturation = 0.0 + case .brightness: + color.brightness = 0.0 + default: () + } + color.alpha = 100.0 + + return color + } + + func maximumColorModel(forSliderAt index: Int) -> SheetyColorProtocol { + guard let slider = SliderType(rawValue: index), let color = colorModel.copy() as? HSBAColor else { + fatalError() + } + + switch slider { + case .hue: + color.hue = 360.0 + case .saturation: + color.saturation = 100.0 + case .brightness: + color.brightness = 100.0 + default: () + } + color.alpha = 100.0 + + return color + } + + func thumbText(forSliderAt index: Int) -> String? { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + switch slider { + case .hue: + return "H" + case .saturation: + return "S" + case .brightness: + return "B" + case .alpha: + return "%" + } + } + + func thumbIconName(forSliderAt _: Int) -> String? { + return nil + } + + func sliderValueChanged(forSliderAt index: Int, value: CGFloat) { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + switch slider { + case .hue: + colorModel.hue = floor(value) + case .saturation: + colorModel.saturation = floor(value) + case .brightness: + colorModel.brightness = floor(value) + case .alpha: + colorModel.alpha = floor(value) + } + + viewModelDelegate?.didUpdateColorComponent(in: self) + } +} diff --git a/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift b/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift index 7b8ec9e..5d8bdde 100644 --- a/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift +++ b/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift @@ -12,7 +12,7 @@ public extension UIColor { /// The RGBAColor representation of the UIColor instance. var rgbaColor: RGBAColor { func normalize(_ component: CGFloat, multiplier: CGFloat) -> CGFloat { - let nomralizedValue = component * multiplier + let nomralizedValue = floor(component * multiplier) if nomralizedValue > multiplier { return multiplier } if nomralizedValue < 0.0 { return 0.0 } diff --git a/SheetyColors/Classes/RGB/Models/RGBSliderType.swift b/SheetyColors/Classes/RGB/Models/RGBSliderType.swift deleted file mode 100644 index 1f6c86a..0000000 --- a/SheetyColors/Classes/RGB/Models/RGBSliderType.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// RGBSliderType.swift -// SheetyColors -// -// Created by Christoph Wendt on 31.03.19. -// - -import Foundation - -enum RGBSliderType: Int, CaseIterable { - case red, green, blue, alpha -} diff --git a/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift b/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift index 06f7060..80c2e12 100644 --- a/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift +++ b/SheetyColors/Classes/RGB/ViewModels/RGBViewModel.swift @@ -5,6 +5,10 @@ // Created by Christoph Wendt on 03.02.19. // +private enum SliderType: Int, CaseIterable { + case red, green, blue, alpha +} + class RGBViewModel { var isAlphaEnabled: Bool var colorModel: RGBAColor @@ -41,12 +45,16 @@ extension RGBViewModel: SheetyColorsViewModelProtocol { return isAlphaEnabled ? 4 : 3 } + func rainbowEnabled(forSliderAt _: Int) -> Bool { + return false + } + func stepInterval(forSliderAt _: Int) -> CGFloat { return 1.0 } func value(forSliderAt index: Int) -> CGFloat { - guard let slider = RGBSliderType(rawValue: index) else { fatalError() } + guard let slider = SliderType(rawValue: index) else { fatalError() } switch slider { case .red: @@ -61,14 +69,14 @@ extension RGBViewModel: SheetyColorsViewModelProtocol { } func maximumValue(forSliderAt index: Int) -> CGFloat { - guard let slider = RGBSliderType(rawValue: index) else { fatalError() } + guard let slider = SliderType(rawValue: index) else { fatalError() } let maxValue: CGFloat = (slider == .alpha) ? 100.0 : 255.0 return maxValue } func minimumColorModel(forSliderAt index: Int) -> SheetyColorProtocol { - guard let slider = RGBSliderType(rawValue: index) else { fatalError() } + guard let slider = SliderType(rawValue: index) else { fatalError() } if case .alpha = slider { return RGBAColor(red: 255.0, green: 255.0, blue: 255.0, alpha: 100.0) } @@ -90,7 +98,7 @@ extension RGBViewModel: SheetyColorsViewModelProtocol { } func maximumColorModel(forSliderAt index: Int) -> SheetyColorProtocol { - guard let slider = RGBSliderType(rawValue: index), let color = colorModel.copy() as? RGBAColor else { + guard let slider = SliderType(rawValue: index), let color = colorModel.copy() as? RGBAColor else { fatalError() } @@ -109,7 +117,7 @@ extension RGBViewModel: SheetyColorsViewModelProtocol { } func thumbText(forSliderAt index: Int) -> String? { - guard let slider = RGBSliderType(rawValue: index) else { fatalError() } + guard let slider = SliderType(rawValue: index) else { fatalError() } switch slider { case .red: @@ -128,7 +136,7 @@ extension RGBViewModel: SheetyColorsViewModelProtocol { } func sliderValueChanged(forSliderAt index: Int, value: CGFloat) { - guard let slider = RGBSliderType(rawValue: index) else { fatalError() } + guard let slider = SliderType(rawValue: index) else { fatalError() } switch slider { case .red: