Reputation: 12625
I am using custom control to trigger the presentation of a UIColorPickerViewController
. It triggers the color picker, but not as expected.
I was using UIColorWell previously, which does present the picker correctly (but it is an opaque implementation so I don't know how it does so). I don't want to use UIColorWell, because the shape and appearance aren't right for where I'm invoking the picker from.
Everything works exactly as desired except I can't get the color picker to present at the bottom of the screen like UIColorWell
would do it. Instead, whatever I've tried to present (or show), UIColorPickerController
appears near the top of the screen.
Note: Currently, to present the color picker controller, I'm using the latest iOS 15 trick with sheet controller / detents to try to anchor the picker to the bottom of the screen, but it's not working as expected (it's the technique is from the Apple Docs example, and as I've seen documented online).
What might be happening? What can I do to get the picker at the bottom of the screen, so that it doesn't obstruct my interface?
The Extension:
extension UIView {
func findViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder
} else if let nextResponder = self.next as? UIView {
return nextResponder.findViewController()
} else {
return nil
}
}
}
Reproducible example (almost. It needs to be implemented/invoked from whatever VC [not shown here]):
This is custom control that that creates a rectangular color picker view (I'm using it to replace UIColorWell). It attempts to locate the VC which contains the view of which this custom control is a subview.
import UIKit
import QuartzCore
class RectangularColorWell : UIControl {
var colorPickerController = UIColorPickerViewController()
var selectedColor : UIColor = UIColor.clear
var lineWidth : CGFloat = 4.0 {
didSet {
self.setNeedsDisplay()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
colorPickerController.supportsAlpha = false
colorPickerController.delegate = self
selectedColor = backgroundColor ?? UIColor.clear
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(CustomToggleControl.controlTapped(_:)))
tapGestureRecognizer.numberOfTapsRequired = 1;
tapGestureRecognizer.numberOfTouchesRequired = 1;
self.addGestureRecognizer(tapGestureRecognizer)
self.setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.setLineWidth(lineWidth)
ctx.setFillColor(backgroundColor!.cgColor)
ctx.fill(rect)
drawGradientBorder(rect, context: ctx)
}
func drawGradientBorder(_ rect: CGRect, context: CGContext) {
context.saveGState()
context.setLineWidth(lineWidth)
let path = UIBezierPath(rect: rect)
context.addPath(path.cgPath)
context.replacePathWithStrokedPath()
context.clip()
let rainbowColors : [UIColor] = [ .red, .orange, .yellow, .green, .blue, .purple ]
let colorDistribution : [CGFloat] = [ 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ]
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: rainbowColors.map { $0.cgColor } as CFArray, locations: colorDistribution)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0, y: 0), end: CGPoint(x: rect.width, y: rect.height), options: [])
context.restoreGState()
}
@objc func controlTapped(_ gestureRecognizer :UIGestureRecognizer) {
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.alpha = 0.5
}, completion: { (finished: Bool) -> Void in
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
self.alpha = 1.0
}, completion: { (finished: Bool) -> Void in
})
})
setNeedsDisplay()
let vc = self.findViewController()
if let sheet = vc?.sheetPresentationController {
sheet.detents = [.medium()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
vc?.present(colorPickerController, animated: true, completion: nil)
sendActions(for: .touchDown)
}
}
extension RectangularColorWell : UIColorPickerViewControllerDelegate {
func colorPickerViewControllerDidFinish(_ controller : UIColorPickerViewController) {
self.selectedColor = controller.selectedColor
}
func colorPickerViewController(_ controller : UIColorPickerViewController, didSelect color: UIColor, continuously: Bool) {
self.backgroundColor = color
self.setNeedsDisplay()
}
}
Upvotes: 0
Views: 882
Reputation: 12625
@JordanH's final comment caused me to read up on the show/present methods on UIViewController docs. The default presentation style was what I'd been using (e.g. I hadn't set any of the presentation options on the color picker's view controller when I filed the question.
Surprisingly after reading that I could control the style, setting the picker's modalPresentationStyle
property to '.pageSheet' which is the one that looks like it would work, to do a partial covering, did not work!.
The solution turned out to be adding colorPickerController.modalPresentationStyle = .popover
before calling present()
in the custom RectangularColorWellView). It now behaves as if it was presented from a UIColorWell control view.
Upvotes: 0