UIImageView isUserInteractionEnabled = true doesn't work - swift

The following DateField class has a date field and an icon on it. For some reason fieldIcon.isUserInteractionEnabled = true has zero affect and doesn't pass user tap to the field. Am I missing something?
import UIKit
import SnapKit
protocol DateFieldDelegate: class {
func dateSelected(_ dateField: DateField, date: Date)
}
class DateField: UIView, UITextFieldDelegate {
weak var delegate: DateFieldDelegate?
let field = UITextField()
private var datePicker: UIDatePicker?
convenience init(withStartDate startDate: Date) {
self.init()
datePicker = prepareDatePicker()
guard let datePicker = datePicker else { return }
field.inputView = datePicker
field.delegate = self
addSubview(field)
field.snp.makeConstraints { make in
make.top.equalToSuperview()
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.height.equalTo(37)
make.bottom.equalToSuperview()
}
let fieldIcon = UIImageView(image: UIImage(asset: Asset.calendar))
field.addSubview(fieldIcon)
fieldIcon.isUserInteractionEnabled = true // doesn't work
fieldIcon.snp.makeConstraints { make in
make.width.equalTo(21)
make.height.equalTo(23)
make.trailing.equalToSuperview().offset(-10)
make.centerY.equalToSuperview()
}
setStyle(.regular)
}
func textFieldDidEndEditing(_ textField: UITextField) {
guard let date = datePicker?.date else { return }
field.text = date.formatToString()
delegate?.dateSelected(self, date: date)
}
private func prepareDatePicker() -> UIDatePicker {
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
datePicker.setDate(Date(), animated: false)
datePicker.locale = Language.appLocale()
return datePicker
}
}

Not sure if I've missed something in your code but you need to add some kind of action to the image, enabling userInteractionEnabled does not tell it to recognise taps on the item and what to do when this happens. You need to add a UITapGestureRecogniser to the UIImageView
let fieldIcon = UIImageView(image: UIImage(asset: Asset.calendar))
field.addSubview(fieldIcon)
fieldIcon.isUserInteractionEnabled = true // doesn't work
let fieldIcon = UIImageView(image: UIImage(asset: Asset.calendar))
field.addSubview(fieldIcon)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(calenderIconTapped))
fieldIcon.addGestureRecognisor(tapGesture)
func calenderIconTapped() {
// show the picker
}
UPDATE:
After re-reading your question, I understand what your attempting to do now. If you want any taps on the image view to passthrough to the UITextField underneath you need to do the opposite of what you tried... you need to set userInteractionEnabled to false on the UIImageView
fieldIcon.isUserInteractionEnabled = false

Related

How to change the text color of a compact UIDatePicker

I have a compact date picker that has nearly unreadable text when the app is light mode.
Is there a way to change the color of it?
Note, I've tried the following:
setting the "textColor" key path, but that only works for the wheels date picker
setting the tintColor, but that only changes the highlight color
It looks like you can't set a custom color for the date picker text, but you can force the date picker to always be in dark mode
datePicker.overrideUserInterfaceStyle = .dark
I found a solution which is more like a hack. I've spent a lot of time, hope it will be useful for somebody. Here is a code:
class MyCompactDatePicker: UIDatePicker {
var textColorToSet = UIColor.magenta //default color, can be set from outside
private var lblDate: UILabel?
private var lblTime: UILabel?
private var actionId: UIAction.Identifier?
override func draw(_ rect: CGRect) {
if preferredDatePickerStyle != .compact {
preferredDatePickerStyle = .compact
}
super.draw(rect)
findLabels()
colorLabels()
if actionId == nil {
let action = UIAction(title: "colorLabels", handler: { [weak self] _ in
DispatchQueue.main.async { //without this dispatch delay color will not be ours when switching between date and time
self?.colorLabels()
}
})
actionId = action.identifier
addAction(action, for: .allEvents)
}
}
private func colorLabels() {
lblDate?.textColor = textColorToSet
lblTime?.textColor = textColorToSet
}
private func findLabels() {
if lblDate == nil || lblTime == nil {
var n = 0
recursFindLabels(v: self, number: &n)
}
}
private func recursFindLabels(v: UIView, number: inout Int) {
if let lbl = v as? UILabel {
switch number {
case 0:
lblDate = lbl
case 1:
lblTime = lbl
default:
return
}
number += 1
if number > 1 { //only first 2 labels are Date and Time labels
return
}
}
for sv in v.subviews {
recursFindLabels(v: sv, number: &number)
}
}
}
and set from ViewController
class ViewController: UIViewController {
#IBOutlet weak var datePicker: MyCompactDatePicker!
override func viewDidLoad() {
super.viewDidLoad()
datePicker.textColorToSet = .green
}
}
The date and time will be in green.
Note: the tintColor will also be overridden by green but it's not a big deal.

iOS 14 UIColorPicker eyedropper tool not returning selected color

I am currently trying to implement the new iOS 14 UIColorPicker. Everything works great, except the eye dropper functionality of the UIColorPicker. After selecting the the eye dropper and sampling a color, the default behavior should be to re-open the UIColorPicker with your selected color as the active one. For some reason, this does not happen. Here is my implementation, its pretty standard, so I'm not sure why the eyedropper isnt behaving as expected.
I have these functions that are passed as the selectors for when I have some UI elements pressed
#objc func pickColorSky(sender: UIControl){
presentColorPicker(tag: 1, sender: sender)
}
#objc func pickColorBackground(sender: UIControl){
presentColorPicker(tag: 2, sender: sender)
}
#objc func pickColorGround(sender: UIControl){
presentColorPicker(tag: 3, sender: sender)
}
Here is the function that presents the UIColorPickerView itself
#objc private func presentColorPicker(tag: Int, sender: UIControl){
let vc = UIColorPickerViewController()
vc.supportsAlpha = false
vc.delegate = self
vc.view.tag = tag
vc.modalPresentationStyle = .popover
vc.popoverPresentationController?.sourceView = sender
vc.popoverPresentationController?.sourceRect = sender.bounds
self.present(vc, animated: true)
}
And the delegate methods to handle the interacting with the color picker
extension myViewController: UIColorPickerViewControllerDelegate {
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
guard let options = editor?.apiView?.getRenderingOptions() else { return }
if viewController.view.tag == 1 {
let newColor = getMobileApiColor(color: viewController.selectedColor)
options.skyColor = newColor
skyRow.color.backgroundColor = newColor.uiColor
}
else if viewController.view.tag == 2 {
let newColor = getMobileApiColor(color: viewController.selectedColor)
options.backgroundColor = newColor
backgroundRow.color.backgroundColor = newColor.uiColor
}
else if viewController.view.tag == 3 {
let newColor = getMobileApiColor(color: viewController.selectedColor)
options.groundColor = newColor
groundRow.color.backgroundColor = newColor.uiColor
}
editor?.modelView?.setNeedsDisplay()
}
Try this method colorPickerViewControllerDidSelectColor
extension ViewController: UIColorPickerViewControllerDelegate {
// Called once you have finished picking the color.
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
self.view.backgroundColor = viewController.selectedColor
}
// Called on every color selection done in the picker.
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
self.view.backgroundColor = viewController.selectedColor
}
}
You need to hold on to the picker object as a class member like this:
private lazy var colorPicker = makeColorPicker()
private func makeColorPicker() -> UIColorPickerViewController {
let vc = UIColorPickerViewController()
vc.delegate = self
vc.supportsAlpha = false
return vc
}
Otherwise, it is removed from memory before you complete the eye-dropper step.

Centering a view with auto constraints - Swift

I am creating an XIB/class of datePicker which is called programatically from a calling viewController when the user taps a button. How can I use auto constraints to place this view immediately below and aligned with the centre of the calling button. My code works in portrait, but fails when the device is rotated (the datePicker does not re-center). Probably because I am passing a CGRect as an argument on init of the view, which doesn't change on rotate. I can't see any other way of overriding the passing of CGRect. When I add in auto constraints code, I get run-time auto formatting errors.
ViewController:
#IBAction func showMyDatePicker(_ sender: Any) {
showMyDatePicker.isEnabled = false
let today = Date()
let minDate = Calendar.current.date(byAdding: .month, value: -3, to: today)
let maxDate = Calendar.current.date(byAdding: .month, value: 3, to: today)
let datePickerWidth = 300
let datePickerHeight = 200
let datePickerX = Int(showMyDatePicker.center.x - CGFloat(datePickerWidth / 2))
let datePickerY = Int(showMyDatePicker.center.y + CGFloat(showMyDatePicker.bounds.height / 2))
let frame = CGRect(x: datePickerX, y: datePickerY, width: datePickerWidth, height: datePickerHeight)
myDatePicker = MyDatePicker(frame: frame)
myDatePicker?.setMyDatePicker(date: today, minimumDate: minDate!, maximumDate: maxDate!)
myDatePicker?.delegate = self
self.view.addSubview(myDatePicker!)
}
datePickerClass:
class MyDatePicker: UIView {
var delegate: MyDatePickerDelegate?
#IBOutlet var contentView: UIView!
#IBOutlet weak var datePicker: UIDatePicker!
#IBOutlet weak var returnButton: UIButton!
#IBAction func datePickerChanged(_ sender: Any) {
delegate?.dateFromDatePicker(date: datePicker.date, closeDatePickerView: false)
}
#IBAction func returnButtonTapped(_ sender: Any) {
delegate?.dateFromDatePicker(date: datePicker.date, closeDatePickerView: true)
}
override init(frame: CGRect) {
super.init(frame: frame)
initView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initView()
}
private func initView() {//QUESTION: how can I set the datepicker initial values within here
// Instantiate the view from xib file
let contentView = Bundle.main.loadNibNamed("MyDatePicker", owner: self, options: nil)?.first as? UIView
// Check that it's not nil
guard contentView != nil else {
return
}
// Add the view and set its frame
addSubview(contentView!)
contentView?.frame = self.bounds
contentView?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
func setMyDatePicker(date: Date, minimumDate: Date, maximumDate: Date) {
datePicker.date = date
datePicker.minimumDate = minimumDate
datePicker.maximumDate = maximumDate
}
}
Just add the required constraint after addSubview and setting TAMIC to false as follows:
#IBAction func showMyDatePicker(_ sender: UIButton) {
// ...
view.addSubview(myDatePicker!)
myDatePicker.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
myDatePicker.topAnchor.constraint(equalTo: sender.bottomAnchor),
myDatePicker.centerXAnchor.constraint(equalTo: sender.centerXAnchor)])
}

How to add two value by using UserDefault

I'm trying to make a simple ToDo app in Swift.
I want to add not only task but also Date,
but I don't know how to add two values to a variable.
I want to add a text which get from UIDatePicker to TodoAdded.
Could you give me any advise please?
import UIKit
//Variable
var TodoAdded = [String]()
class AddController: UIViewController {
//TextField
#IBOutlet weak var TodoTextField: UITextField!
//TextField for Date
#IBOutlet weak var DateTextField: UITextField!
var datePicker: UIDatePicker = UIDatePicker()
//追加ボタンの設定
#IBAction func TodoAddButten(_ sender: Any) {
//Add typed text to variable
TodoAdded.append(TodoTextField.text!)
//Empty after tapped button
TodoTextField.text = ""
//Add to UD
UserDefaults.standard.set( TodoAdded, forKey: "TodoList" )
}
override func viewDidLoad() {
super.viewDidLoad()
// Picker Setting
datePicker.datePickerMode = UIDatePicker.Mode.dateAndTime
datePicker.timeZone = NSTimeZone.local
datePicker.locale = Locale.current
DateTextField.inputView = datePicker
//
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 35))
let spacelItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done))
toolbar.setItems([spacelItem, doneItem], animated: true)
// InputView
DateTextField.inputView = datePicker
DateTextField.inputAccessoryView = toolbar
}
//
#objc func done() {
DateTextField.endEditing(true)
// Format
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
DateTextField.text = "\(formatter.string(from: Date()))"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
You can try to save it as [String:[String]]
UserDefaults.standard.set([dateTextField.text!:todoAdded], forKey: "TodoList" )
A good approach is
struct Root:Codable {
let date:Date
let tasks:[String]
}
Then use JSONDecoder / JSONEncoder to convert to object / data , after that you can easily save / read them

Target-Action problems with custom view built from standard views

I have a custom view subclassing NSView, which is just an NSStackView containing a label, slider, a second label and a checkbox. The slider and checkbox are both configured to report changes to the view (and eventually, via a delegate to a ViewController):
fileprivate extension NSTextField {
static func label(text: String? = nil) -> NSTextField {
let label = NSTextField()
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.drawsBackground = false
label.stringValue = text ?? ""
return label
}
}
#IBDesignable
class Adjustable: NSView {
private let sliderLabel = NSTextField.label()
private let slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
private let valueLabel = NSTextField.label()
private let enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))
var valueFormatter: (Double)->(String) = { String(format:"%5.2f", $0) }
...
#objc func sliderChanged(_ sender: Any) {
guard let slider = sender as? NSSlider else { return }
valueLabel.stringValue = valueFormatter(slider.doubleValue)
print("Slider now: \(slider.doubleValue)")
delegate?.adjustable(self, changedValue: slider.doubleValue)
}
#objc func enabledChanged(_ sender: Any) {
guard let checkbox = sender as? NSButton else { return }
print("Enabled now: \(checkbox.state == .on)")
delegate?.adjustable(self, changedEnabled: checkbox.state == .on)
}
}
Using InterfaceBuilder, I can add one instance of this to a ViewController by dragging in a CustomView and setting it's class in the Identity Inspector. Toggling the checkbox or changing the slider will have the desired effect.
However, if I have multiple instances then in the target-action functions self will always refer to the same instance of the view, rather than the one being interacted with. In other words, self.slider == sender is only true in sliderChanged for one of the sliders. While I can get the correct slider value via sender, I cannot update the correct label as self.valueLabel is always the label in the first instance of the custom view.
Incidentally, #IBDesignable and the code intended to support it have no effect so there's something I'm missing there too - Interface Builder just shows empty space.
The whole file:
import Cocoa
fileprivate extension NSTextField {
static func label(text: String? = nil) -> NSTextField {
let label = NSTextField()
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.drawsBackground = false
label.stringValue = text ?? ""
return label
}
}
protocol AdjustableDelegate {
func adjustable(_ adjustable: Adjustable, changedEnabled: Bool)
func adjustable(_ adjustable: Adjustable, changedValue: Double)
}
#IBDesignable
class Adjustable: NSView {
var delegate: AdjustableDelegate? = nil
private let sliderLabel = NSTextField.label()
private let slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
private let valueLabel = NSTextField.label()
private let enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))
var valueFormatter: (Double)->(String) = { String(format:"%5.2f", $0) }
#IBInspectable
var label: String = "" {
didSet {
sliderLabel.stringValue = label
}
}
#IBInspectable
var value: Double = 0 {
didSet {
slider.doubleValue = value
valueLabel.stringValue = valueFormatter(value)
}
}
#IBInspectable
var enabled: Bool = false {
didSet {
enabledCheckbox.isEnabled = enabled
}
}
#IBInspectable
var minimum: Double = 0 {
didSet {
slider.minValue = minimum
}
}
#IBInspectable
var maximum: Double = 100 {
didSet {
slider.maxValue = maximum
}
}
#IBInspectable
var tickMarks: Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
setup()
}
override func prepareForInterfaceBuilder() {
setup()
}
override func awakeFromNib() {
setup()
}
private func setup() {
let stack = NSStackView()
stack.orientation = .horizontal
stack.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(sliderLabel)
stack.addArrangedSubview(slider)
stack.addArrangedSubview(valueLabel)
stack.addArrangedSubview(enabledCheckbox)
sliderLabel.stringValue = label
slider.doubleValue = value
valueLabel.stringValue = valueFormatter(value)
slider.minValue = minimum
slider.maxValue = maximum
slider.numberOfTickMarks = tickMarks
// Make the slider be the one that expands to fill available space
slider.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 249), for: .horizontal)
sliderLabel.widthAnchor.constraint(equalToConstant: 60).isActive = true
valueLabel.widthAnchor.constraint(equalToConstant: 60).isActive = true
addSubview(stack)
stack.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
stack.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
stack.topAnchor.constraint(equalTo: topAnchor).isActive = true
stack.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
#objc func sliderChanged(_ sender: Any) {
guard let slider = sender as? NSSlider else { return }
valueLabel.stringValue = valueFormatter(slider.doubleValue)
print("Slider now: \(slider.doubleValue)")
delegate?.adjustable(self, changedValue: slider.doubleValue)
}
#objc func enabledChanged(_ sender: Any) {
guard let checkbox = sender as? NSButton else { return }
print("Enabled now: \(checkbox.state == .on)")
delegate?.adjustable(self, changedEnabled: checkbox.state == .on)
}
}
The solution, as described in the question linked by Willeke, was to ensure init had completed before referencing self. (I'm slightly surprised the compiler allowed it to be used in a property initialiser)
Wrong:
private let slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
private let enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))
Right:
private lazy var slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
private lazy var enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))