-
-
Save Amzd/62160c84a29ae93d565b20f63b9e3247 to your computer and use it in GitHub Desktop.
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 | |
} | |
} | |
} |
Thank you! This helped tremendously. Some changes I made to ColorPickerSheet
which may help others:
import SwiftUI
extension View {
@available(iOS 15.0, *)
public func colorPickerSheet(isPresented: Binding<Bool>, selection: Binding<Color>, supportsAlpha: Bool = false, title: String? = nil, action: @escaping () -> Void) -> some View {
self.background(ColorPickerSheet(isPresented: isPresented, selection: selection, supportsAlpha: supportsAlpha, title: title, action: action))
}
}
@available(iOS 15.0, *)
private struct ColorPickerSheet: UIViewRepresentable {
@Binding var isPresented: Bool
@Binding var selection: Color
var supportsAlpha: Bool
var title: String?
var action: () -> Void
func makeCoordinator() -> Coordinator {
Coordinator(selection: $selection, isPresented: $isPresented, action: action)
}
class Coordinator: NSObject, UIColorPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
@Binding var selection: Color
@Binding var isPresented: Bool
var didPresent = false
var action: () -> Void
init(selection: Binding<Color>, isPresented: Binding<Bool>, action: @escaping () -> Void) {
self._selection = selection
self._isPresented = isPresented
self.action = action
}
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
selection = Color(viewController.selectedColor)
action()
}
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
}
}
}
@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
}
@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()
.
Thank you, this is still very helpful! But is there a way to display the picker inside a popover instead of a sheet?
@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.
@pianostringquartet hmm yea I didnt write this with crossplatform code in mind
I hated working with the ColorPicker SwiftUI implementation so I wrote two fixes.
ColorPickerWithoutLabel
which speaks for itself; it doesn't have a label.View.colorPickerSheet
it boggles my mind that you cannot use ColorPicker as just a sheet by default, I must be missing something. Anyway, I wrote it myself with UIKit under the hood.