Skip to content

Instantly share code, notes, and snippets.

@Amzd
Last active Nov 13, 2022
Embed
What would you like to do?
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

JTostitos commented Aug 31, 2022

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

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