Skip to content

Instantly share code, notes, and snippets.

@Amzd
Last active October 27, 2023 16:02
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Amzd/62160c84a29ae93d565b20f63b9e3247 to your computer and use it in GitHub Desktop.
Save Amzd/62160c84a29ae93d565b20f63b9e3247 to your computer and use it in GitHub Desktop.
What SwiftUI's ColorPicker should have been.
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
}
}
}
@Amzd
Copy link
Author

Amzd commented Aug 31, 2022

@JTostitos Seems you added a callback for selection changes?

You could just use SwiftUIs onChange?

From the top of my head it could be something like this

.colorPickerSheet(..., selection: $color, ...)
.onChange(of: color) { newColor in
    // do whatever you wanted to do in the action block
}

@JTostitos
Copy link

@Amzd Sorry, I didn't realize that my explanation got removed when I updated my code in my comment. What I was trying to do was make it so that when you tap a color, it automatically dismisses but that caused a few other issues with buttons that are displayed on the view behind the Color Picker and so it wasn't worth it for me to try and fix it. I ended up leaving the callback though because 3 less lines of code in my view if it is part of the .colorPickerSheet() itself instead of using .onChange().

@stoobit
Copy link

stoobit commented Jul 20, 2023

Thank you, this is still very helpful! But is there a way to display the picker inside a popover instead of a sheet?

@pianostringquartet
Copy link

pianostringquartet commented Oct 25, 2023

@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.

Screenshot 2023-10-25 at 4 32 40 PM

Screenshot 2023-10-25 at 4 32 54 PM

@Amzd
Copy link
Author

Amzd commented Oct 27, 2023

@pianostringquartet hmm yea I didnt write this with crossplatform code in mind

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment