Last active
January 25, 2025 08:49
-
-
Save Amzd/62160c84a29ae93d565b20f63b9e3247 to your computer and use it in GitHub Desktop.
What SwiftUI's ColorPicker should have been.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
@available(iOS 14.0, *) | |
public struct ColorPickerWithoutLabel: UIViewRepresentable { | |
@Binding var selection: Color | |
var supportsAlpha: Bool = true | |
public init(selection: Binding<Color>, supportsAlpha: Bool = true) { | |
self._selection = selection | |
self.supportsAlpha = supportsAlpha | |
} | |
public func makeUIView(context: Context) -> UIColorWell { | |
let well = UIColorWell() | |
well.supportsAlpha = supportsAlpha | |
return well | |
} | |
public func updateUIView(_ uiView: UIColorWell, context: Context) { | |
uiView.selectedColor = UIColor(selection) | |
} | |
} | |
extension View { | |
@available(iOS 14.0, *) | |
public func colorPickerSheet(isPresented: Binding<Bool>, selection: Binding<Color>, supportsAlpha: Bool = true, title: String? = nil) -> some View { | |
self.background(ColorPickerSheet(isPresented: isPresented, selection: selection, supportsAlpha: supportsAlpha, title: title)) | |
} | |
} | |
@available(iOS 14.0, *) | |
private struct ColorPickerSheet: UIViewRepresentable { | |
@Binding var isPresented: Bool | |
@Binding var selection: Color | |
var supportsAlpha: Bool | |
var title: String? | |
func makeCoordinator() -> Coordinator { | |
Coordinator(selection: $selection, isPresented: $isPresented) | |
} | |
class Coordinator: NSObject, UIColorPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate { | |
@Binding var selection: Color | |
@Binding var isPresented: Bool | |
var didPresent = false | |
init(selection: Binding<Color>, isPresented: Binding<Bool>) { | |
self._selection = selection | |
self._isPresented = isPresented | |
} | |
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) { | |
selection = Color(viewController.selectedColor) | |
} | |
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) { | |
isPresented = false | |
didPresent = false | |
} | |
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { | |
isPresented = false | |
didPresent = false | |
} | |
} | |
func getTopViewController(from view: UIView) -> UIViewController? { | |
guard var top = view.window?.rootViewController else { | |
return nil | |
} | |
while let next = top.presentedViewController { | |
top = next | |
} | |
return top | |
} | |
func makeUIView(context: Context) -> UIView { | |
let view = UIView() | |
view.isHidden = true | |
return view | |
} | |
func updateUIView(_ uiView: UIView, context: Context) { | |
if isPresented && !context.coordinator.didPresent { | |
let modal = UIColorPickerViewController() | |
modal.selectedColor = UIColor(selection) | |
modal.supportsAlpha = supportsAlpha | |
modal.title = title | |
modal.delegate = context.coordinator | |
modal.presentationController?.delegate = context.coordinator | |
let top = getTopViewController(from: uiView) | |
top?.present(modal, animated: true) | |
context.coordinator.didPresent = true | |
} | |
} | |
} |
@pianostringquartet hmm yea I didnt write this with crossplatform code in mind
On iOS, this works perfectly, just with a renaming of the supportsOpacity
to supportsAlpha
argument.
I posted a Stackoverflow question and answer, in case anyone else might find this useful, and linked to this post, @Amzd .
It fixed my issue that had me scratching my head wondering why I couldn't align my color pickers, due to the blank label actually taking up space (can see below in before vs. after screenshots of implementing this ColorPickerWithoutLabel
:
Before:

Turns out, using your struct ColorPickerWithoutLabel
broke my code. I believe this is due to the fact that yours only supports Color
types, while Swift's also supports CGColor
s :
public struct ColorPicker<Label> : View where Label : View {
/// Creates an instance that selects a color.
///
/// - Parameters:
/// - selection: A ``Binding`` to the variable that displays the
/// selected ``Color``.
/// - supportsOpacity: A Boolean value that indicates whether the color
/// picker allows adjusting the selected color's opacity; the default
/// is `true`.
/// - label: A view that describes the use of the selected color.
/// The system color picker UI sets it's title using the text from
/// this view.
///
public init(selection: Binding<Color>, supportsOpacity: Bool = true, @ViewBuilder label: () -> Label)
/// Creates an instance that selects a color.
///
/// - Parameters:
/// - selection: A ``Binding`` to the variable that displays the
/// selected ``CGColor``.
/// - supportsOpacity: A Boolean value that indicates whether the color
/// picker allows adjusting the selected color's opacity; the default
/// is `true`.
/// - label: A view that describes the use of the selected color.
/// The system color picker UI sets it's title using the text from
/// this view.
///
public init(selection: Binding<CGColor>, supportsOpacity: Bool = true, @ViewBuilder label: () -> Label)
/// The content and behavior of the view.
///
/// When you implement a custom view, you must implement a computed
/// `body` property to provide the content for your view. Return a view
/// that's composed of built-in views that SwiftUI provides, plus other
/// composite views that you've already defined:
///
/// struct MyView: View {
/// var body: some View {
/// Text("Hello, World!")
/// }
/// }
///
/// For more information about composing views and a view hierarchy,
/// see <doc:Declaring-a-Custom-View>.
@MainActor public var body: some View { get }
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required ``View/body-swift.property`` property.
public typealias Body = some View
}
Thank you so much!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@Amzd thank you for this!
I've noticed that on Catalyst, it first opens up a sheet with just a tiny color selection box in the top left corner. Then pressing the "show colors" button, we get the regular Mac color picker.