diff --git a/CHANGELOG.md b/CHANGELOG.md index 21abff3..25ebfd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Version 0.3.0 +### Features +* Grayscale support + ## Version 0.2.0 ### Features * HSB support diff --git a/Documentation/Reference/README.md b/Documentation/Reference/README.md index 5891521..98be691 100644 --- a/Documentation/Reference/README.md +++ b/Documentation/Reference/README.md @@ -1,4 +1,6 @@ # Reference Documentation +This Reference Documentation has been generated with +[SourceDocs](https://github.com/eneko/SourceDocs). ## Protocols @@ -11,6 +13,7 @@ ## Classes +- [GrayscaleColor](classes/GrayscaleColor.md) - [HSBAColor](classes/HSBAColor.md) - [RGBAColor](classes/RGBAColor.md) @@ -20,6 +23,7 @@ ## Extensions +- [GrayscaleColor](extensions/GrayscaleColor.md) - [HSBAColor](extensions/HSBAColor.md) - [RGBAColor](extensions/RGBAColor.md) - [SheetyColorsController](extensions/SheetyColorsController.md) diff --git a/Documentation/Reference/classes/GrayscaleColor.md b/Documentation/Reference/classes/GrayscaleColor.md new file mode 100644 index 0000000..d3e745b --- /dev/null +++ b/Documentation/Reference/classes/GrayscaleColor.md @@ -0,0 +1,50 @@ +**CLASS** + +# `GrayscaleColor` + +```swift +public class GrayscaleColor: NSObject, NSCopying, Codable +``` + +> A model class representing grayscale colors. The white component can hold values between 0.0 and 255.0 while the alphavalue has a maximum value of 100.0. + +## Methods +### `init(white:alpha:)` + +```swift +public init(white: CGFloat, alpha: CGFloat) +``` + +> Creates a GrayscaleColor instance. +> +> - Parameter: +> - white: The white component. +> - alpha: The opacity component. + +### `copy(with:)` + +```swift +public func copy(with _: NSZone? = nil) -> Any +``` + +> Creates a copy of the GrayscaleColor instance. +> +> - Returns: A copy of the GrayscaleColor instance. + +### `isEqual(_:)` + +```swift +public override func isEqual(_ object: Any?) -> Bool +``` + +> Compares two GrayscaleColor instances with each other. +> +> - Parameter object: The GrayscaleColor to compare with. +> +> - Returns: 'true' if the instance is equal to the other GrayscaleColor instance, otherwise 'false''. + +#### Parameters + +| Name | Description | +| ---- | ----------- | +| object | The GrayscaleColor to compare with. | \ No newline at end of file diff --git a/Documentation/Reference/enums/SheetyColorsType.md b/Documentation/Reference/enums/SheetyColorsType.md index a32e1b9..041caeb 100644 --- a/Documentation/Reference/enums/SheetyColorsType.md +++ b/Documentation/Reference/enums/SheetyColorsType.md @@ -9,13 +9,13 @@ public enum SheetyColorsType: Equatable, CaseIterable > An enum used for specifying the color model of the SheetyColors view. ## Cases -### `rgb` +### `grayscale` ```swift -case rgb +case grayscale ``` -> The RGB color model. +> The grayscale color model. ### `hsb` @@ -24,3 +24,11 @@ case hsb ``` > The HSB color model. + +### `rgb` + +```swift +case rgb +``` + +> The RGB color model. diff --git a/Documentation/Reference/extensions/GrayscaleColor.md b/Documentation/Reference/extensions/GrayscaleColor.md new file mode 100644 index 0000000..b4cd432 --- /dev/null +++ b/Documentation/Reference/extensions/GrayscaleColor.md @@ -0,0 +1,12 @@ +**EXTENSION** + +# `GrayscaleColor` + +## Properties +### `uiColor` + +```swift +public var uiColor: UIColor +``` + +> The UIColor representation of the GrayscaleColor. diff --git a/Documentation/Reference/extensions/UIColor.md b/Documentation/Reference/extensions/UIColor.md index 7b2ad66..68a937f 100644 --- a/Documentation/Reference/extensions/UIColor.md +++ b/Documentation/Reference/extensions/UIColor.md @@ -3,6 +3,14 @@ # `UIColor` ## Properties +### `grayscaleColor` + +```swift +var grayscaleColor: GrayscaleColor +``` + +> The GrayscaleColor representation of the UIColor instance. + ### `hsbaColor` ```swift diff --git a/Documentation/sheetycolors.gif b/Documentation/sheetycolors.gif index 13cb96c..6ef140c 100644 Binary files a/Documentation/sheetycolors.gif and b/Documentation/sheetycolors.gif differ diff --git a/Example/Podfile b/Example/Podfile index 05e9d4b..c128f09 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -1,5 +1,5 @@ use_frameworks! -platform :ios, '10.0' +platform :ios, '11.0' target 'SheetyColors_Example' do pod 'SheetyColors', :path => '../' diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 6ad1cff..c8196f3 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -30,6 +30,6 @@ SPEC CHECKSUMS: SheetyColors: 483d95d270f01dde725b68217b63704f1efc984b SnapshotTesting: 9ca1d80f6322509a035856170c11d0571b1f7171 -PODFILE CHECKSUM: 5b2abbedbad9c9292a41e99328101b079df5f152 +PODFILE CHECKSUM: 9b39eb683f19af0f28a510f2ff148ddd0ce5f2a5 COCOAPODS: 1.6.0.rc.2 diff --git a/Example/SheetyColors.xcodeproj/project.pbxproj b/Example/SheetyColors.xcodeproj/project.pbxproj index 7649bce..e68f4d1 100644 --- a/Example/SheetyColors.xcodeproj/project.pbxproj +++ b/Example/SheetyColors.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 834A36ED224E8C7400DECD37 /* SheetyColorsViewModelDelegateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834A36EC224E8C7400DECD37 /* SheetyColorsViewModelDelegateMock.swift */; }; 834A36F2224E94FC00DECD37 /* SheetyColorsViewModelMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834A36F1224E94FC00DECD37 /* SheetyColorsViewModelMock.swift */; }; 834A36F4224E98F900DECD37 /* SheetyColorsViewMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834A36F3224E98F900DECD37 /* SheetyColorsViewMock.swift */; }; + 83537FC222739E1400E91C39 /* UIColor+grayscaleColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83537FC122739E1400E91C39 /* UIColor+grayscaleColorTests.swift */; }; + 83537FC52273A01000E91C39 /* GrayscaleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83537FC42273A01000E91C39 /* GrayscaleViewModel.swift */; }; 83681FE82231AC14000AB720 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83681FE72231AC14000AB720 /* Storage.swift */; }; 83DDE21A225FBCBC005F4CEF /* UIColor+rgbaColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDE219225FBCBC005F4CEF /* UIColor+rgbaColorTests.swift */; }; 83DDE21C2262204F005F4CEF /* GradientSliderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDE21B2262204F005F4CEF /* GradientSliderTests.swift */; }; @@ -76,6 +78,8 @@ 834A36EC224E8C7400DECD37 /* SheetyColorsViewModelDelegateMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetyColorsViewModelDelegateMock.swift; sourceTree = ""; }; 834A36F1224E94FC00DECD37 /* SheetyColorsViewModelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetyColorsViewModelMock.swift; sourceTree = ""; }; 834A36F3224E98F900DECD37 /* SheetyColorsViewMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetyColorsViewMock.swift; sourceTree = ""; }; + 83537FC122739E1400E91C39 /* UIColor+grayscaleColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+grayscaleColorTests.swift"; sourceTree = ""; }; + 83537FC42273A01000E91C39 /* GrayscaleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrayscaleViewModel.swift; sourceTree = ""; }; 83681FE72231AC14000AB720 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; 83DDE219225FBCBC005F4CEF /* UIColor+rgbaColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+rgbaColorTests.swift"; sourceTree = ""; }; 83DDE21B2262204F005F4CEF /* GradientSliderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientSliderTests.swift; sourceTree = ""; }; @@ -180,6 +184,7 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( + 83537FBE22739D8700E91C39 /* Grayscale */, 832DCEFC226E4D5900DAF3EF /* HSB */, 834A36EA224E893B00DECD37 /* Common */, 834A36EB224E894900DECD37 /* RGB */, @@ -289,9 +294,9 @@ 834A36EA224E893B00DECD37 /* Common */ = { isa = PBXGroup; children = ( + 834A36E9224E88FB00DECD37 /* Factories */, 83DDE21F2264FD06005F4CEF /* Models */, 83205EE22256812D00D62BB8 /* Views */, - 834A36E9224E88FB00DECD37 /* Factories */, ); name = Common; sourceTree = ""; @@ -305,6 +310,39 @@ name = RGB; sourceTree = ""; }; + 83537FBE22739D8700E91C39 /* Grayscale */ = { + isa = PBXGroup; + children = ( + 83537FC322739FFA00E91C39 /* ViewModels */, + 83537FBF22739D9600E91C39 /* Models */, + ); + name = Grayscale; + sourceTree = ""; + }; + 83537FBF22739D9600E91C39 /* Models */ = { + isa = PBXGroup; + children = ( + 83537FC022739DF600E91C39 /* Extensions */, + ); + name = Models; + sourceTree = ""; + }; + 83537FC022739DF600E91C39 /* Extensions */ = { + isa = PBXGroup; + children = ( + 83537FC122739E1400E91C39 /* UIColor+grayscaleColorTests.swift */, + ); + name = Extensions; + sourceTree = ""; + }; + 83537FC322739FFA00E91C39 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 83537FC42273A01000E91C39 /* GrayscaleViewModel.swift */, + ); + name = ViewModels; + sourceTree = ""; + }; 83DDE217225FBC99005F4CEF /* Models */ = { isa = PBXGroup; children = ( @@ -605,6 +643,8 @@ 834A36E2224971F700DECD37 /* SheetyColorsViewFactoryTests.swift in Sources */, 831FCF0022591DFE003C48A4 /* UIAlertController+customViewTests.swift in Sources */, 831FCF02225E62F8003C48A4 /* SheetyColorsViewTests.swift in Sources */, + 83537FC52273A01000E91C39 /* GrayscaleViewModel.swift in Sources */, + 83537FC222739E1400E91C39 /* UIColor+grayscaleColorTests.swift in Sources */, 834A36E8224E88F400DECD37 /* RGBViewModelTests.swift in Sources */, 834A36ED224E8C7400DECD37 /* SheetyColorsViewModelDelegateMock.swift in Sources */, 83205EE52256815500D62BB8 /* UIView+findLabelTests.swift in Sources */, diff --git a/Example/SheetyColors/ColorsCollectionViewController.swift b/Example/SheetyColors/ColorsCollectionViewController.swift index 5757861..f8f95ca 100644 --- a/Example/SheetyColors/ColorsCollectionViewController.swift +++ b/Example/SheetyColors/ColorsCollectionViewController.swift @@ -50,6 +50,9 @@ class ColorsCollectionViewController: UICollectionViewController { 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: "Grayscale", style: .default, handler: { _ in + self.openColorPicker(withAction: action, colorType: .grayscale) + })) colorTypeSelectionSheet.addAction(UIAlertAction(title: "HSB", style: .default, handler: { _ in self.openColorPicker(withAction: action, colorType: .hsb) })) diff --git a/Example/Tests/GrayscaleViewModel.swift b/Example/Tests/GrayscaleViewModel.swift new file mode 100644 index 0000000..d251920 --- /dev/null +++ b/Example/Tests/GrayscaleViewModel.swift @@ -0,0 +1,226 @@ +// +// GrayscaleViewModel.swift +// SheetyColors_Tests +// +// Created by Christoph Wendt on 26.04.19. +// Copyright © 2019 CocoaPods. All rights reserved. +// + +import Nimble +import Quick +@testable import SheetyColors + +class GrayscaleViewModelTests: QuickSpec { + override func spec() { + describe("The GrayscaleViewModel") { + var sut: GrayscaleViewModel! + var delegateMock: SheetyColorsViewModelDelegateMock! + + context("after initialization") { + var testColorModel: GrayscaleColor! + var testIsAlphaEnabled: Bool! + + beforeEach { + delegateMock = SheetyColorsViewModelDelegateMock() + testIsAlphaEnabled = true + testColorModel = GrayscaleColor(white: 123.0, alpha: 69.0) + sut = GrayscaleViewModel(withColorModel: testColorModel, alphaEnabled: testIsAlphaEnabled) + sut.viewModelDelegate = delegateMock + } + + it("sets up the instance correctly") { + expect(sut).to(beAnInstanceOf(GrayscaleViewModel.self)) + } + + context("when calling primaryKeyText property") { + it("returns string Grayscale") { + expect(sut!.primaryKeyText).to(equal("Grayscale")) + } + } + + context("when calling primaryValueText property") { + it("returns string representation of grascale values") { + let expectedText = "\(Int(testColorModel.white)) \(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? GrayscaleColor).to(equal(testColorModel)) + } + } + + context("when calling numberOfSliders property") { + it("returns 2") { + expect(sut.numberOfSliders).to(equal(2)) + } + } + + 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("when calling value()") { + context("with index 0") { + it("returns the color model's white component value") { + expect(sut.value(forSliderAt: 0)).to(equal(testColorModel.white)) + } + } + + context("with index 1") { + it("returns the color model's alpha component value") { + expect(sut.value(forSliderAt: 1)).to(equal(testColorModel.alpha)) + } + } + } + + context("when calling maximumValue()") { + context("with index 0") { + it("returns 255.0") { + expect(sut.maximumValue(forSliderAt: 0)).to(equal(255.0)) + } + } + + context("with index 1") { + it("returns 100.0") { + expect(sut.maximumValue(forSliderAt: 1)).to(equal(100.0)) + } + } + } + + context("when calling minimumColorModel()") { + context("with index 0") { + it("returns correct min color model with white component of 0.0 and full opacity") { + let actual = sut.minimumColorModel(forSliderAt: 0) as! GrayscaleColor + let expected = testColorModel.copy() as! GrayscaleColor + expected.white = 0.0 + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + + context("with index 1") { + it("returns color model representing white color") { + let actual = sut.minimumColorModel(forSliderAt: 1) as! GrayscaleColor + let expected = GrayscaleColor(white: 255.0, alpha: 100.0) + + expect(actual).to(equal(expected)) + } + } + } + + context("when calling maximumColorModel()") { + context("with index 0") { + it("returns correct max color model with white component of 255.0 and full opacity") { + let actual = sut.maximumColorModel(forSliderAt: 0) as! GrayscaleColor + let expected = testColorModel.copy() as! GrayscaleColor + expected.white = 255.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: 1) as! GrayscaleColor + let expected = testColorModel.copy() as! GrayscaleColor + expected.alpha = 100.0 + + expect(actual).to(equal(expected)) + } + } + } + + context("when calling thumbText()") { + context("with index 0") { + it("returns W") { + expect(sut.thumbText(forSliderAt: 0)).to(equal("W")) + } + } + + context("with index 1") { + it("returns %") { + expect(sut.thumbText(forSliderAt: 1)).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("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.white).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 alpha component of the color model") { + expect(sut.colorModel.alpha).to(equal(floor(testColorValue))) + } + } + } + } + } + } +} diff --git a/Example/Tests/SheetyColorsViewFactoryTests.swift b/Example/Tests/SheetyColorsViewFactoryTests.swift index cf4edd1..e12605c 100644 --- a/Example/Tests/SheetyColorsViewFactoryTests.swift +++ b/Example/Tests/SheetyColorsViewFactoryTests.swift @@ -30,6 +30,42 @@ class SheetyColorsViewFactoryTests: QuickSpec { expect(viewModel.colorModel).to(equal(testConfig.initialColor.rgbaColor)) } } + + context("when calling create with a config of type .hsb") { + var testConfig: SheetyColorsConfigProtocol! + var view: SheetyColorsView! + + beforeEach { + testConfig = SheetyColorsConfigMock(alphaEnabled: true, initialColor: .red, hapticFeedbackEnabled: true, title: "", type: .hsb) + view = SheetyColorsViewFactory.createView(withConfig: testConfig) + } + + it("returns a SheetyColorsView with HSB components") { + expect(view.viewModel).to(beAnInstanceOf(HSBViewModel.self)) + + let viewModel = view.viewModel as! HSBViewModel + expect(viewModel.isAlphaEnabled).to(equal(testConfig.alphaEnabled)) + expect(viewModel.colorModel).to(equal(testConfig.initialColor.hsbaColor)) + } + } + + context("when calling create with a config of type .grayscale") { + var testConfig: SheetyColorsConfigProtocol! + var view: SheetyColorsView! + + beforeEach { + testConfig = SheetyColorsConfigMock(alphaEnabled: true, initialColor: .red, hapticFeedbackEnabled: true, title: "", type: .grayscale) + view = SheetyColorsViewFactory.createView(withConfig: testConfig) + } + + it("returns a SheetyColorsView with HSB components") { + expect(view.viewModel).to(beAnInstanceOf(GrayscaleViewModel.self)) + + let viewModel = view.viewModel as! GrayscaleViewModel + expect(viewModel.isAlphaEnabled).to(equal(testConfig.alphaEnabled)) + expect(viewModel.colorModel).to(equal(testConfig.initialColor.grayscaleColor)) + } + } } } } diff --git a/Example/Tests/SheetyColorsViewTests.swift b/Example/Tests/SheetyColorsViewTests.swift index cf6cd24..54f2727 100644 --- a/Example/Tests/SheetyColorsViewTests.swift +++ b/Example/Tests/SheetyColorsViewTests.swift @@ -112,6 +112,36 @@ class SheetyColorsViewTests: QuickSpec { assertSnapshot(matching: sut, as: .image(size: .init(width: 600, height: 400))) } } + + 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, alphaEnabled: false) + sut = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: false) + viewModel.viewModelDelegate = 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))) + } + } + + 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, alphaEnabled: true) + sut = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: false) + viewModel.viewModelDelegate = 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))) + } + } } } } diff --git a/Example/Tests/UIColor+grayscaleColorTests.swift b/Example/Tests/UIColor+grayscaleColorTests.swift new file mode 100644 index 0000000..71e9c8e --- /dev/null +++ b/Example/Tests/UIColor+grayscaleColorTests.swift @@ -0,0 +1,52 @@ +// +// UIColor+grayscaleColorTests.swift +// SheetyColors_Tests +// +// Created by Christoph Wendt on 26.04.19. +// Copyright © 2019 CocoaPods. All rights reserved. +// + +import Nimble +import Quick +@testable import SheetyColors + +class UIColorGrayscaleColorTests: QuickSpec { + override func spec() { + describe("The UIColor+grayscaleColor extension") { + var sut: GrayscaleColor! + + context("when calling grayscaleColor on a gray colorspace color") { + beforeEach { + sut = UIColor(white: 0.5, alpha: 0.5).grayscaleColor + } + + it("returns an HSBAColor instance") { + expect(sut.white).to(equal(127.0)) + expect(sut.alpha).to(equal(50.0)) + } + } + + context("when calling grayscaleColor on a rgb colorspace color") { + beforeEach { + sut = UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5).grayscaleColor + } + + it("returns an GraycaleColor instance") { + expect(sut.white).to(equal(72.0)) + expect(sut.alpha).to(equal(50.0)) + } + } + + context("when calling grayscaleColor on a rgb extended colorspace color") { + beforeEach { + sut = UIColor(red: 2.0, green: -1.0, blue: 1.0, alpha: 2.0).grayscaleColor + } + + it("returns an GraycaleColor instance") { + expect(sut.white).to(equal(72.0)) + expect(sut.alpha).to(equal(100.0)) + } + } + } + } +} diff --git a/Example/Tests/UIColor+hsbaColorTests.swift b/Example/Tests/UIColor+hsbaColorTests.swift index bcb28fe..f589780 100644 --- a/Example/Tests/UIColor+hsbaColorTests.swift +++ b/Example/Tests/UIColor+hsbaColorTests.swift @@ -20,7 +20,7 @@ class UIColorHsbaColorTests: QuickSpec { sut = UIColor(hue: 0.1, saturation: 0.25, brightness: 0.75, alpha: 0.5).hsbaColor } - it("returns an HSBA color model") { + it("returns an HSBAColor instance") { expect(sut.hue).to(equal(36.0)) expect(sut.saturation).to(equal(25.0)) expect(sut.brightness).to(equal(75.0)) @@ -33,7 +33,7 @@ class UIColorHsbaColorTests: QuickSpec { sut = UIColor(white: 1.0, alpha: 0.5).hsbaColor } - it("returns an HSBA color model") { + it("returns an HSBAColor instance") { expect(sut.saturation).to(equal(0.0)) expect(sut.brightness).to(equal(100.0)) } @@ -44,7 +44,7 @@ class UIColorHsbaColorTests: QuickSpec { sut = UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5).hsbaColor } - it("returns an HSBA color model") { + it("returns an HSBAColor instance") { expect(sut.hue).to(equal(300.0)) expect(sut.saturation).to(equal(100.0)) expect(sut.brightness).to(equal(100.0)) @@ -57,7 +57,7 @@ class UIColorHsbaColorTests: QuickSpec { sut = UIColor(red: 2.0, green: -1.0, blue: 1.0, alpha: 2.0).hsbaColor } - it("returns an HSBA color model") { + it("returns an HSBAColor instance") { expect(sut.hue).to(equal(320.0)) expect(sut.saturation).to(equal(100.0)) expect(sut.brightness).to(equal(100.0)) diff --git a/Example/Tests/UIColor+rgbaColorTests.swift b/Example/Tests/UIColor+rgbaColorTests.swift index 4abd3da..a18f7c7 100644 --- a/Example/Tests/UIColor+rgbaColorTests.swift +++ b/Example/Tests/UIColor+rgbaColorTests.swift @@ -20,7 +20,7 @@ class UIColorRgbaColorTests: QuickSpec { sut = UIColor(white: 1.0, alpha: 0.5).rgbaColor } - it("returns an RGBA color model") { + it("returns an RGBAColor instance") { expect(sut.red).to(equal(255.0)) expect(sut.green).to(equal(255.0)) expect(sut.blue).to(equal(255.0)) @@ -33,7 +33,7 @@ class UIColorRgbaColorTests: QuickSpec { sut = UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5).rgbaColor } - it("returns an RGBA color model") { + it("returns an RGBAColor instance") { expect(sut.red).to(equal(255.0)) expect(sut.green).to(equal(0.0)) expect(sut.blue).to(equal(255.0)) @@ -46,7 +46,7 @@ class UIColorRgbaColorTests: QuickSpec { sut = UIColor(red: 2.0, green: -1.0, blue: 1.0, alpha: 2.0).rgbaColor } - it("returns an RGBA color model") { + it("returns an RGBAColor instance") { expect(sut.red).to(equal(255.0)) expect(sut.green).to(equal(0.0)) expect(sut.blue).to(equal(255.0)) diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.16.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.16.txt new file mode 100644 index 0000000..c761094 --- /dev/null +++ b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.16.txt @@ -0,0 +1,19 @@ +> + | > + | | ; 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.17.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.17.png new file mode 100644 index 0000000..555a91e Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.17.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.18.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.18.png new file mode 100644 index 0000000..d8f4058 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.18.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.19.txt b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.19.txt new file mode 100644 index 0000000..66ab74a --- /dev/null +++ b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.19.txt @@ -0,0 +1,24 @@ +> + | > + | | ; 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.20.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.20.png new file mode 100644 index 0000000..b972dd6 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.20.png differ diff --git a/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.21.png b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.21.png new file mode 100644 index 0000000..3e5a3f4 Binary files /dev/null and b/Example/Tests/__Snapshots__/SheetyColorsViewTests/spec.21.png differ diff --git a/README.md b/README.md index 6271a96..d79c1a0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![Cocoapods platforms](https://img.shields.io/cocoapods/p/SheetyColors.svg) -![Cocoapods](https://img.shields.io/cocoapods/v/SheetyColors.svg) +![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) [![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) @@ -11,7 +11,7 @@ **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 (RGB or HSB), alpha component support, haptic feedback, and many more. +- **Fully configurable:** You can choose between a variety of configurations such as a color model (RGB, HSB, or Grayscale), 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.

@@ -79,7 +79,7 @@ present(sheetyColors, animated: true, completion: nil) ``` -Please check the [documentation](./Documentation/Reference/README.MD) for further information on the API. +Please check the [documentation](./Documentation/Reference/README.md) for further information on the API. ## Contributions diff --git a/Scripts/release.sh b/Scripts/release.sh old mode 100644 new mode 100755 index 588f594..51d626f --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -4,11 +4,11 @@ #============================= Linting & Formatting ============================= echo "Running SwiftFormat" -swift run swiftformat . +swift run swiftformat ../ echo "Running SwiftLint" -swift run swiftlint autocorrect --path SheetyColors/ +swift run swiftlint autocorrect --path ../ #================================= Documentation ================================ echo "Generating docs with SourceDocs" -swift run sourcedocs generate -- -workspace Example/SheetyColors.xcworkspace -scheme SheetyColors \ No newline at end of file +swift run sourcedocs generate --output-folder ../Documentation/Reference -- -workspace ../Example/SheetyColors.xcworkspace -scheme SheetyColors \ No newline at end of file diff --git a/SheetyColors.podspec b/SheetyColors.podspec index 047bedc..3a0c829 100644 --- a/SheetyColors.podspec +++ b/SheetyColors.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SheetyColors' - s.version = '0.2.0' + s.version = '0.3.0' s.summary = 'An action sheet styled color picker for iOS.' s.description = <<-DESC @@ -10,8 +10,9 @@ The SheetyColors color picker is based on UIKit's UIAlertController. Therefore, s.homepage = 'https://github.com/chrs1885/SheetyColors' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'chrs1885' => 'christoph.wendt@me.com' } + s.documentation_url = 'https://github.com/chrs1885/SheetyColors/blob/master/Documentation/Reference/README.md' s.source = { :git => 'https://github.com/chrs1885/SheetyColors.git', :tag => s.version.to_s } - # s.social_media_url = 'https://twitter.com/chr_wendt' + s.social_media_url = 'https://twitter.com/chr_wendt' s.ios.deployment_target = '11.0' s.dependency 'Capable/Colors', '~> 0.9.0' diff --git a/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift b/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift index 7839283..a38a4bb 100644 --- a/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift +++ b/SheetyColors/Classes/Common/Factory/SheetyColorsViewFactory.swift @@ -12,10 +12,12 @@ struct SheetyColorsViewFactory { var viewModel: SheetyColorsViewModelProtocol switch config.type { - case .rgb: - viewModel = RGBViewModel(withColorModel: config.initialColor.rgbaColor, alphaEnabled: config.alphaEnabled) + case .grayscale: + viewModel = GrayscaleViewModel(withColorModel: config.initialColor.grayscaleColor, alphaEnabled: config.alphaEnabled) case .hsb: viewModel = HSBViewModel(withColorModel: config.initialColor.hsbaColor, alphaEnabled: config.alphaEnabled) + case .rgb: + viewModel = RGBViewModel(withColorModel: config.initialColor.rgbaColor, alphaEnabled: config.alphaEnabled) } let view = SheetyColorsView(withViewModel: viewModel, hapticFeedbackEnabled: config.hapticFeedbackEnabled) diff --git a/SheetyColors/Classes/Common/Models/Extensions/CGFloat+constrainToRange.swift b/SheetyColors/Classes/Common/Models/Extensions/CGFloat+constrainToRange.swift new file mode 100644 index 0000000..5e90a60 --- /dev/null +++ b/SheetyColors/Classes/Common/Models/Extensions/CGFloat+constrainToRange.swift @@ -0,0 +1,24 @@ +// +// CGFloat+constrainToRange.swift +// SheetyColors +// +// Created by Christoph Wendt on 25.04.19. +// + +import Foundation + +extension CGFloat { + mutating func constrainTo(min: CGFloat = 0.0, max: CGFloat) { + if self > max { + self = max + } else if self < min { + self = min + } + } + + mutating func normalizeTo(max: CGFloat) { + self *= max + constrainTo(max: max) + round(.down) + } +} diff --git a/SheetyColors/Classes/Common/Models/SheetyColorsType.swift b/SheetyColors/Classes/Common/Models/SheetyColorsType.swift index 1d26d8f..086d88f 100644 --- a/SheetyColors/Classes/Common/Models/SheetyColorsType.swift +++ b/SheetyColors/Classes/Common/Models/SheetyColorsType.swift @@ -7,9 +7,12 @@ /// An enum used for specifying the color model of the SheetyColors view. public enum SheetyColorsType: Equatable, CaseIterable { - /// The RGB color model. - case rgb + /// The grayscale color model. + case grayscale /// The HSB color model. case hsb + + /// The RGB color model. + case rgb } diff --git a/SheetyColors/Classes/Grayscale/Models/Extensions/UIColor+grayscaleColor.swift b/SheetyColors/Classes/Grayscale/Models/Extensions/UIColor+grayscaleColor.swift new file mode 100644 index 0000000..f5515a2 --- /dev/null +++ b/SheetyColors/Classes/Grayscale/Models/Extensions/UIColor+grayscaleColor.swift @@ -0,0 +1,30 @@ +// +// UIColor+grayscaleColor.swift +// SheetyColors +// +// Created by Christoph Wendt on 25.04.19. +// + +import Capable +import UIKit + +/// Extends UIColor with functionality to convert an instance to a GrayscaleColor. +public extension UIColor { + /// The GrayscaleColor representation of the UIColor instance. + var grayscaleColor: GrayscaleColor { + var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 + + if getRed(&red, green: &green, blue: &blue, alpha: &alpha) { + red.constrainTo(max: 1.0) + green.constrainTo(max: 1.0) + blue.constrainTo(max: 1.0) + var luminocity: CGFloat = 0.2126 * red + 0.7152 * green + 0.0722 * blue + luminocity.normalizeTo(max: 255.0) + alpha.normalizeTo(max: 100.0) + + return GrayscaleColor(white: luminocity, alpha: alpha) + } else { + return GrayscaleColor(white: 0.0, alpha: 100.0) + } + } +} diff --git a/SheetyColors/Classes/Grayscale/Models/GrayscaleColor.swift b/SheetyColors/Classes/Grayscale/Models/GrayscaleColor.swift new file mode 100644 index 0000000..ef3a754 --- /dev/null +++ b/SheetyColors/Classes/Grayscale/Models/GrayscaleColor.swift @@ -0,0 +1,66 @@ +// +// GrayscaleColor.swift +// SheetyColors +// +// Created by Christoph Wendt on 25.04.19. +// + +import CoreGraphics + +/// A model class representing grayscale colors. The white component can hold values between 0.0 and 255.0 while the alphavalue has a maximum value of 100.0. +public class GrayscaleColor: NSObject, NSCopying, Codable { + var white, alpha: CGFloat + + var hexColor: String { + let rgb: Int = Int(white) << 16 | Int(white) << 8 | Int(white) << 0 + + return String(format: "%06x", rgb).uppercased() + } + + /** + Creates a GrayscaleColor instance. + + - Parameter: + - white: The white component. + - alpha: The opacity component. + */ + public init(white: CGFloat, alpha: CGFloat) { + self.white = white + self.alpha = alpha + } + + /** + Creates a copy of the GrayscaleColor instance. + + - Returns: A copy of the GrayscaleColor instance. + */ + public func copy(with _: NSZone? = nil) -> Any { + let copy = GrayscaleColor(white: white, alpha: alpha) + return copy + } + + /** + Compares two GrayscaleColor instances with each other. + + - Parameter object: The GrayscaleColor to compare with. + + - Returns: 'true' if the instance is equal to the other GrayscaleColor instance, otherwise 'false''. + */ + public override func isEqual(_ object: Any?) -> Bool { + guard let otherColor = object as? GrayscaleColor else { + return false + } + + return white == otherColor.white && alpha == otherColor.alpha + } +} + +// MARK: - Converting to other color models + +/// An extension adding functionality defined in SheetyColorProtocol to GrayscaleColor. +extension GrayscaleColor: SheetyColorProtocol { + /// The UIColor representation of the GrayscaleColor. + public var uiColor: UIColor { + return UIColor(white: white / 255.0, alpha: alpha / 100.0) + } +} diff --git a/SheetyColors/Classes/Grayscale/ViewModels/GrayscaleViewModel.swift b/SheetyColors/Classes/Grayscale/ViewModels/GrayscaleViewModel.swift new file mode 100644 index 0000000..6521248 --- /dev/null +++ b/SheetyColors/Classes/Grayscale/ViewModels/GrayscaleViewModel.swift @@ -0,0 +1,129 @@ +// +// GrayscaleViewModel.swift +// SheetyColors +// +// Created by Christoph Wendt on 25.04.19. +// + +private enum SliderType: Int, CaseIterable { + case white, alpha +} + +class GrayscaleViewModel { + var isAlphaEnabled: Bool + var colorModel: GrayscaleColor + weak var viewModelDelegate: SheetyColorsViewModelDelegate? + + init(withColorModel colorModel: GrayscaleColor, alphaEnabled: Bool) { + self.colorModel = colorModel + isAlphaEnabled = alphaEnabled + } +} + +extension GrayscaleViewModel: SheetyColorsViewModelProtocol { + var primaryKeyText: String { + return "Grayscale" + } + + var primaryValueText: String { + return "\(Int(colorModel.white)) \(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 _: Int) -> Bool { + return false + } + + 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 .white: + return colorModel.white + case .alpha: + return colorModel.alpha + } + } + + func maximumValue(forSliderAt index: Int) -> CGFloat { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + switch slider { + case .white: + return 255.0 + case .alpha: + return 100.0 + } + } + + func minimumColorModel(forSliderAt index: Int) -> SheetyColorProtocol { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + switch slider { + case .white: + return GrayscaleColor(white: 0.0, alpha: 100.0) + case .alpha: + return GrayscaleColor(white: 255.0, alpha: 100.0) + } + } + + func maximumColorModel(forSliderAt index: Int) -> SheetyColorProtocol { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + switch slider { + case .white: + return GrayscaleColor(white: 255.0, alpha: 100.0) + case .alpha: + return GrayscaleColor(white: colorModel.white, alpha: 100.0) + } + } + + func thumbText(forSliderAt index: Int) -> String? { + guard let slider = SliderType(rawValue: index) else { fatalError() } + + switch slider { + case .white: + return "W" + 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 .white: + colorModel.white = floor(value) + case .alpha: + colorModel.alpha = floor(value) + } + + viewModelDelegate?.didUpdateColorComponent(in: self) + } +} diff --git a/SheetyColors/Classes/HSB/Models/Extensions/UIColor+hsbaColor.swift b/SheetyColors/Classes/HSB/Models/Extensions/UIColor+hsbaColor.swift index cf1d42a..2114d00 100644 --- a/SheetyColors/Classes/HSB/Models/Extensions/UIColor+hsbaColor.swift +++ b/SheetyColors/Classes/HSB/Models/Extensions/UIColor+hsbaColor.swift @@ -11,25 +11,17 @@ import UIKit 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) + hue.normalizeTo(max: 360.0) + saturation.normalizeTo(max: 100.0) + brightness.normalizeTo(max: 100.0) + alpha.normalizeTo(max: 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) + return HSBAColor(hue: 0.0, saturation: 100.0, brightness: 100.0, alpha: 100.0) } } } diff --git a/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift b/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift index 5d8bdde..d54fa7a 100644 --- a/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift +++ b/SheetyColors/Classes/RGB/Models/Extensions/UIColor+rgbaColor.swift @@ -11,25 +11,17 @@ import UIKit public extension UIColor { /// The RGBAColor representation of the UIColor instance. var rgbaColor: RGBAColor { - 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 red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 if getRed(&red, green: &green, blue: &blue, alpha: &alpha) { - let red = normalize(red, multiplier: 255.0) - let green = normalize(green, multiplier: 255.0) - let blue = normalize(blue, multiplier: 255.0) - let alpha = normalize(alpha, multiplier: 100.0) + red.normalizeTo(max: 255.0) + green.normalizeTo(max: 255.0) + blue.normalizeTo(max: 255.0) + alpha.normalizeTo(max: 100.0) return RGBAColor(red: red, green: green, blue: blue, alpha: alpha) } else { - return RGBAColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1) + return RGBAColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 100.0) } } }