├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── PresentableColorPicker
│ ├── Notification.Name+Extensions.swift
│ ├── PresentableColorPicker+Modifiers.swift
│ ├── PresentableColorPicker.swift
│ ├── Resources
│ ├── en-US.lproj
│ │ └── Localizable.strings
│ └── en.lproj
│ │ └── Localizable.strings
│ └── UIKitColorPicker.swift
└── Tests
├── LinuxMain.swift
└── PresentableColorPickerTests
├── PresentableColorPickerTests.swift
└── XCTestManifests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Franklyn Weber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "HalfASheet",
6 | "repositoryURL": "https://github.com/franklynw/HalfASheet.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "2f1959c5e8a74cb078b96942d46388b7e4bebcdb",
10 | "version": "1.0.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "PresentableColorPicker",
8 | defaultLocalization: "en",
9 | platforms: [
10 | .iOS(.v14)
11 | ],
12 | products: [
13 | .library(
14 | name: "PresentableColorPicker",
15 | targets: ["PresentableColorPicker"]),
16 | ],
17 | dependencies: [
18 | .package(name: "HalfASheet", url: "https://github.com/franklynw/HalfASheet.git", .upToNextMajor(from: "1.0.0"))
19 | ],
20 | targets: [
21 | .target(
22 | name: "PresentableColorPicker",
23 | dependencies: ["HalfASheet"],
24 | resources: [.process("Resources")]),
25 | .testTarget(
26 | name: "PresentableColorPickerTests",
27 | dependencies: ["PresentableColorPicker"]),
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PresentableColorPicker
2 |
3 | A Colour Picker pretty much identical to Apple's ColorPicker (in that it uses the UIColorPickerViewController), except that this one can be presented in the way common to sheets, etc, with a bound 'isPresented' boolean var.
4 |
5 |
6 | ## Installation
7 |
8 | ### Swift Package Manager
9 |
10 | In Xcode:
11 | * File ⭢ Swift Packages ⭢ Add Package Dependency...
12 | * Use the URL https://github.com/franklynw/PresentableColorPicker
13 |
14 |
15 | ## Example
16 |
17 | > **NB:** All examples require `import PresentableColorPicker` at the top of the source file
18 |
19 | It can be used directly as a view, which offers the full range of customisation options -
20 |
21 | ```swift
22 | var body: some View {
23 |
24 | PresentableColorPicker(isPresented: $isStandaloneColorPickerPresented) {
25 | viewModel.paintColor = $0
26 | }
27 | .backgroundColor(viewModel.backgroundColor)
28 | .disableDismissOnSelection
29 | }
30 | ```
31 |
32 | or as a modifier, which presents the default colour picker (with no customisation options) -
33 |
34 | ```swift
35 | var body: some View {
36 |
37 | MyView {
38 |
39 | }
40 | .presentableColorPicker(isPresented: $isStandaloneColorPickerPresented, Binding: $viewModel.paintColor)
41 | }
42 | ```
43 |
44 | Both of these methods allow you to specify either a binding to a Color var, or use a 'colorSelected' closure which is invoked when the colour is picked.
45 |
46 |
47 | ### Set the picker's title
48 |
49 | ```swift
50 | PresentableColorPicker(isPresented: $isStandaloneColorPickerPresented, selected: $viewModel.paintColor)
51 | .title("Pick a colour")
52 | ```
53 |
54 | If not used, the title will default to localised "Colour" (ie, if you have "Colour" in your Localizable.strings file, it will use that, otherwise just "Colour")
55 |
56 |
57 | ### Disable the automatic "dismiss on selection" functionality
58 |
59 | This might be necessary if you have (eg) a preview visible above the picker, where you can see how your selected colour looks - the user can then decide when to dismiss the picker
60 |
61 | ```swift
62 | PresentableColorPicker(isPresented: $isStandaloneColorPickerPresented, selected: $viewModel.paintColor)
63 | .disableDismissOnSelection
64 | ```
65 |
66 | ### Set the picker's background colour
67 |
68 | ```swift
69 | PresentableColorPicker(isPresented: $isStandaloneColorPickerPresented, selected: $viewModel.paintColor)
70 | .backgroundColor(.lightGray)
71 | ```
72 |
73 | ### Set the height of the picker as either a fixed height or as a proportion of the containing view's height
74 |
75 | ```swift
76 | PresentableColorPicker(isPresented: $isStandaloneColorPickerPresented, selected: $viewModel.paintColor)
77 | .height(.fixed(400))
78 | ```
79 |
80 | or
81 |
82 | ```swift
83 | PresentableColorPicker(isPresented: $isStandaloneColorPickerPresented, selected: $viewModel.paintColor)
84 | .height(.proportional(0.6))
85 | ```
86 |
87 | ## Additionally...
88 |
89 | There are two NotificationCenter notifications which are sent, which are defined as static vars on Notification.Name -
90 |
91 | * presentableColorPickerAppeared ("PresentableColorPickerAppearedNotification")
92 | * presentableColorPickerDisappeared ("PresentableColorPickerDisappearedNotification")
93 |
94 | These are sent as their names suggest, and there is no additional userInfo
95 |
96 |
97 | ## Dependencies
98 |
99 | Requires HalfASheet, which is linked. Take a look at it [here](https://github.com/franklynw/HalfASheet)
100 |
101 |
102 | ## Licence
103 |
104 | `PresentableColorPicker` is available under the MIT licence
105 |
--------------------------------------------------------------------------------
/Sources/PresentableColorPicker/Notification.Name+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Notification.Name+Extensions.swift
3 | //
4 | //
5 | // Created by Franklyn Weber on 04/02/2021.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 | extension Notification.Name {
12 |
13 | public static let presentableColorPickerAppeared = Notification.Name("PresentableColorPickerAppearedNotification")
14 | public static let presentableColorPickerDisappeared = Notification.Name("PresentableColorPickerDisappearedNotification")
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/PresentableColorPicker/PresentableColorPicker+Modifiers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentableColorPicker+Modifiers.swift
3 | //
4 | //
5 | // Created by Franklyn Weber on 10/02/2021.
6 | //
7 |
8 | import SwiftUI
9 | import HalfASheet
10 |
11 |
12 | extension PresentableColorPicker {
13 |
14 | /// The title to use for the picker
15 | /// - Parameter title: a String
16 | public func title(_ title: String) -> Self {
17 | var copy = self
18 | copy.title = title
19 | return copy
20 | }
21 |
22 | /// The color to use for the background of the picker
23 | /// - Parameter backgroundColor: a UIColor
24 | public func backgroundColor(_ backgroundColor: UIColor) -> Self {
25 | var copy = self
26 | copy.backgroundColor = backgroundColor
27 | return copy
28 | }
29 |
30 | /// Use this for more precise control over the picker's height
31 | /// - Parameter height: a HalfASheetHeight case
32 | public func height(_ height: HalfASheetHeight) -> Self {
33 | var copy = self
34 | copy.height = height
35 | return copy
36 | }
37 |
38 | /// Normally, selecting a colour will dismiss the picker. Use this to disable that functionality (eg if you have a preview which shows how the colour will look in context)
39 | public var disableDismissOnSelection: Self {
40 | var copy = self
41 | copy.dismissOnSelection = false
42 | return copy
43 | }
44 | }
45 |
46 |
47 | extension View {
48 |
49 | /// View extension in the style of .sheet - lacks a couple of customisation options. If more flexibility is required, use PresentableColorPicker(...) directly, and apply the required modifiers
50 | /// - Parameters:
51 | /// - isPresented: binding to a Bool which controls whether or not to show the picker
52 | /// - selection: binding to a Color var for the selected colour
53 | public func presentableColorPicker(isPresented: Binding, selection: Binding) -> some View {
54 | modifier(ColorPickerPresentationModifier(content: { PresentableColorPicker(isPresented: isPresented, selection: selection)}))
55 | }
56 |
57 | /// View extension in the style of .sheet - lacks a couple of customisation options. If more flexibility is required, use PresentableColorPicker(...) directly, and apply the required modifiers
58 | /// - Parameters:
59 | /// - isPresented: binding to a Bool which controls whether or not to show the picker
60 | /// - colorSelected: closure invoked with the selected colour when the user picks a colour
61 | public func presentableColorPicker(isPresented: Binding, colorSelected: @escaping (Color) -> ()) -> some View {
62 | modifier(ColorPickerPresentationModifier(content: { PresentableColorPicker(isPresented: isPresented, colorSelected: colorSelected)}))
63 | }
64 | }
65 |
66 |
67 | struct ColorPickerPresentationModifier: ViewModifier {
68 |
69 | var content: () -> PresentableColorPicker
70 |
71 | init(@ViewBuilder content: @escaping () -> PresentableColorPicker) {
72 | self.content = content
73 | }
74 |
75 | func body(content: Content) -> some View {
76 | ZStack {
77 | content
78 | self.content()
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Sources/PresentableColorPicker/PresentableColorPicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentableColorPicker.swift
3 | // Simplist
4 | //
5 | // Created by Franklyn Weber on 10/02/2021.
6 | //
7 |
8 | import SwiftUI
9 | import HalfASheet
10 |
11 |
12 | public struct PresentableColorPicker: View {
13 |
14 | @Binding private var isPresented: Bool
15 | @Binding private var selection: Color
16 | private var colorSelected: ((Color) -> ())?
17 |
18 | internal var title: String?
19 | internal var backgroundColor: UIColor = .systemBackground
20 | internal var dismissOnSelection = true
21 | internal var height: HalfASheetHeight?
22 |
23 |
24 | public init(isPresented: Binding, selection: Binding) {
25 | _isPresented = isPresented
26 | _selection = selection
27 | colorSelected = nil
28 | }
29 |
30 | public init(isPresented: Binding, colorSelected: @escaping (Color) -> ()) {
31 | _isPresented = isPresented
32 | _selection = Binding(get: { .black }, set: { _ in })
33 | self.colorSelected = colorSelected
34 | }
35 |
36 | public var body: some View {
37 |
38 | HalfASheet(isPresented: $isPresented) {
39 | presentColorPicker()
40 | .background(Color(backgroundColor))
41 | .cornerRadius(15)
42 | .onAppear {
43 | NotificationCenter.default.post(name: .presentableColorPickerAppeared, object: self)
44 | }
45 | .onDisappear {
46 | NotificationCenter.default.post(name: .presentableColorPickerDisappeared, object: self)
47 | }
48 | }
49 | .closeButtonColor(UIColor.gray.withAlphaComponent(0.4))
50 | .height(height ?? .fixed(544))
51 | }
52 |
53 | private func presentColorPicker() -> UIKitColorPicker {
54 |
55 | var picker: UIKitColorPicker
56 |
57 | if let colorSelected = colorSelected {
58 | picker = UIKitColorPicker(isPresented: _isPresented, colorSelected: colorSelected)
59 | } else {
60 | picker = UIKitColorPicker(isPresented: _isPresented, selection: _selection)
61 | }
62 |
63 | picker.dismissOnSelection = dismissOnSelection
64 | picker.title = title
65 |
66 | return picker
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/PresentableColorPicker/Resources/en-US.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | PresentableColorPicker
4 |
5 | Created by Franklyn Weber on 15/01/2021.
6 |
7 | */
8 |
9 | "Colour" = "Color";
10 |
--------------------------------------------------------------------------------
/Sources/PresentableColorPicker/Resources/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | PresentableColorPicker
4 |
5 | Created by Franklyn Weber on 15/01/2021.
6 |
7 | */
8 |
9 | "Colour" = "Colour";
10 |
--------------------------------------------------------------------------------
/Sources/PresentableColorPicker/UIKitColorPicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIKitColorPicker.swift
3 | //
4 | //
5 | // Created by Franklyn Weber on 10/02/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | struct UIKitColorPicker: UIViewControllerRepresentable {
12 |
13 | typealias UIViewControllerType = UIColorPickerViewController
14 |
15 | @Binding private var isPresented: Bool
16 | @Binding private var selection: Color
17 | private var colorSelected: ((Color) -> ())?
18 |
19 | var dismissOnSelection = true
20 | var title: String?
21 |
22 |
23 | init(isPresented: Binding, selection: Binding) {
24 | _isPresented = isPresented
25 | _selection = selection
26 | }
27 |
28 | init(isPresented: Binding, colorSelected: @escaping (Color) -> ()) {
29 | _isPresented = isPresented
30 | _selection = Binding(get: { .black }, set: { _ in })
31 | self.colorSelected = colorSelected
32 | }
33 |
34 |
35 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIViewControllerType {
36 |
37 | let controller = UIColorPickerViewController()
38 | controller.selectedColor = UIColor(selection)
39 | controller.supportsAlpha = false
40 | controller.delegate = context.coordinator
41 | controller.title = title
42 |
43 | return controller
44 | }
45 |
46 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext) {
47 |
48 | }
49 |
50 | func makeCoordinator() -> UIKitColorPicker.Coordinator {
51 | Coordinator(self)
52 | }
53 |
54 | class Coordinator: NSObject, UIColorPickerViewControllerDelegate {
55 |
56 | let parent: UIKitColorPicker
57 |
58 | init(_ parent: UIKitColorPicker) {
59 | self.parent = parent
60 | }
61 |
62 | func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
63 |
64 | let color = Color(viewController.selectedColor)
65 |
66 | parent.colorSelected?(color)
67 | parent.selection = color
68 |
69 | if parent.dismissOnSelection {
70 | parent.isPresented = false
71 | }
72 | }
73 |
74 | func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
75 | parent.isPresented = false
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import PresentableColorPickerTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += PresentableColorPickerTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/PresentableColorPickerTests/PresentableColorPickerTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import PresentableColorPicker
3 |
4 | final class PresentableColorPickerTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | }
10 |
11 | static var allTests = [
12 | ("testExample", testExample),
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/Tests/PresentableColorPickerTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(PresentableColorPickerTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------