0

How can I use the same function to handle DatePickerView on 2 different textFields?

To avoid repetition, I would like to set up 2 textFields with a DatePicker. One field is for issue date and the other one for due date.

When I try to add parameter on @objc func doneClick, I got a compiler error ==> Instance member 'doneClick' cannot be used on type 'InvoiceViewController'; did you mean to use a value of this type instead?

import UIKit

class InvoiceViewController: UIViewController, UITextFieldDelegate{

    var thePicker = UIDatePicker()

    @IBOutlet weak var dateIssueTextField: UITextField!
    @IBOutlet weak var dueDateTextField: UITextField!

    @objc func doneClick(textField: UITextField!) {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .short
        dateFormatter.timeStyle = .none
        dateIssueTextField.text = dateFormatter.string(from: thePicker.date)

        dateFormatter.dateFormat = "yyyy"
        let year: String = dateFormatter.string(from: self.thePicker.date)
        dateFormatter.dateFormat = "MM"
        let month: String = dateFormatter.string(from: self.thePicker.date)
        dateFormatter.dateFormat = "dd"
        let day: String = dateFormatter.string(from: self.thePicker.date)

        let finalDate = year+"-"+month+"-"+day
        print(finalDate)
       // dateIssueTextField.resignFirstResponder()
        textField.resignFirstResponder()
    }

    @objc func cancelClick() {
        dateIssueTextField.resignFirstResponder()
    }

    func setUpTextFieldPicker(textField: UITextField) {
        // DatePicker
        self.thePicker = UIDatePicker(frame:CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 216))
        self.thePicker.backgroundColor = UIColor.white
        self.thePicker.datePickerMode = UIDatePickerMode.date
       // dateIssueTextField.inputView = thePicker
       // dueDateTextField.inputView = thePicker
        textField.inputView = thePicker

        // ToolBar
        let toolBar = UIToolbar()
        toolBar.barStyle = .default
        toolBar.isTranslucent = true
        toolBar.tintColor = UIColor(red: 92/255, green: 216/255, blue: 255/255, alpha: 1)
        toolBar.sizeToFit()

        // Adding Button ToolBar
        let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(InvoiceViewController.doneClick))
        let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(InvoiceViewController.cancelClick))
        toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
        toolBar.isUserInteractionEnabled = true
        //dateIssueTextField.inputAccessoryView = toolBar
        //dueDateTextField.inputAccessoryView = toolBar
        textField.inputAccessoryView = toolBar
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpTextFieldPicker(textField: dateIssueTextField)
        setUpTextFieldPicker(textField: dueDateTextField)

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }



}
Renan Aguiar
  • 245
  • 5
  • 22
  • 1
    It's impossible to call a method with `UITextField` parameter with a action selector of `UIBarButtonItem`. The action method must either have an `UIBarButtonItem` parameter or no parameter. – vadian Feb 21 '18 at 17:27
  • How could I accomplish that using only 1 function to both textFields? Or I cant, and have to make 2 functions? – Renan Aguiar Feb 21 '18 at 17:32
  • And why do you call `dateFormatter.string(from` three times instead of once with format `yyyy-MM-dd`? – vadian Feb 21 '18 at 17:34
  • The 3 calls are because Im still new on swift. Thank you for the advice. – Renan Aguiar Feb 21 '18 at 17:36
  • @RenanAguiar I think it ll help you https://stackoverflow.com/questions/43251708/passing-arguments-to-selector-in-swift3/43252561 – Muhammad Zeshan Arif Feb 21 '18 at 17:38

1 Answers1

2

doneClick cannot have parameter of UITextField type, when it's used as a target to UIBarButtonItem.

You will have to edit more than just that method. The toolbar needs to know to which UITextField it belongs to. So create your custom subclass of UIToolbar, in which you will setup its items, and add a delegate to it which will be sent events on done and cancel pressed:

import UIKit

protocol AccessoryToolbarDelegate: class {
    func doneClicked(for textField: UITextField)
    func cancelClicked(for textField: UITextField)
}

class AccessoryToolbar: UIToolbar {

    fileprivate let textField: UITextField

    weak var accessoryDelegate: AccessoryToolbarDelegate?

    init(for textField: UITextField) {
        self.textField = textField
        super.init(frame: CGRect.zero)

        self.barStyle = .default
        self.isTranslucent = true
        self.tintColor = UIColor(red: 92/255, green: 216/255, blue: 255/255, alpha: 1)
        self.sizeToFit()

        let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(doneClicked))
        let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancelClicked))
        self.setItems([cancelButton, spaceButton, doneButton], animated: false)
        self.isUserInteractionEnabled = true

        textField.inputAccessoryView = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc fileprivate func doneClicked() {
        accessoryDelegate?.doneClicked(for: self.textField)
    }

    @objc fileprivate func cancelClicked() {
        accessoryDelegate?.cancelClicked(for: self.textField)
    }
}

Now with this code you can rewrite your old code to:

import UIKit

class InvoiceViewController: UIViewController, AccessoryToolbarDelegate {
    var thePicker = UIDatePicker()

    @IBOutlet weak var dateIssueTextField: UITextField!
    @IBOutlet weak var dueDateTextField: UITextField!

    func doneClicked(for textField: UITextField) {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .short
        dateFormatter.timeStyle = .none
        // I am not sure if you want this
        textField.text = dateFormatter.string(from: thePicker.date)

        // or rather this, but that's up to you to finish
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let finalDate: String = dateFormatter.string(from: thePicker.date)
        print(finalDate)

        textField.resignFirstResponder()
    }

    func cancelClicked(for textField: UITextField) {
        textField.resignFirstResponder()
    }

    func setUpTextFieldPicker(textField: UITextField) {
        // DatePicker
        self.thePicker = UIDatePicker(frame:CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 216))
        self.thePicker.backgroundColor = UIColor.white
        self.thePicker.datePickerMode = UIDatePickerMode.date
        // dateIssueTextField.inputView = thePicker
        // dueDateTextField.inputView = thePicker
        textField.inputView = thePicker

        // ToolBar
        let toolbar = AccessoryToolbar(for: textField)
        // let's not forget to set the delegate
        toolbar.accessoryDelegate = self
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        setUpTextFieldPicker(textField: dateIssueTextField)
        setUpTextFieldPicker(textField: dueDateTextField)
    }
}
Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
  • With that I cant know which text field is being used. – Renan Aguiar Feb 21 '18 at 17:41
  • @RenanAguiar why do you need to know it? resigningFirstResponder will work anyway – Milan Nosáľ Feb 21 '18 at 17:42
  • Because I have 2 different fields (issue date and due date). As it is now, I can open the picker on both text fields, but it changes only issue date, even if opened on due date – Renan Aguiar Feb 21 '18 at 17:43
  • @RenanAguiar why does it matter? the only code you use textField in is the one calling `resignFirstResponder` on it. by resigning explicitly on both textField you will get the same behavior.. – Milan Nosáľ Feb 21 '18 at 17:45
  • Because issue date is not the same as due date. Two different data. – Renan Aguiar Feb 21 '18 at 17:46
  • Than it kinda goes back to my question. How to know which one using one function and not two. – Renan Aguiar Feb 21 '18 at 17:47
  • to anyone downvoting, thanks too.. I am glad that there are people here trying to help by using constructive criticism – Milan Nosáľ Feb 21 '18 at 17:59
  • @RenanAguiar I forgot to set the delegate to the toolbars, so use the current version - in `InvoiceViewController.setupTextFieldPicker` there is now added line `toolbar.accessoryDelegate = self` – Milan Nosáľ Feb 21 '18 at 18:06
  • Thank you Milan, it is what I was looking for. There is something odd. Due date works fine, issue date, goes back to today date. – Renan Aguiar Feb 21 '18 at 18:17
  • @RenanAguiar I guess it's because of the datepicker being created twice.. I updated the answer (just the `InvoiceViewController` implementation), but basically all you need to do is to move the `datePicker` creation from `setUpTextFieldPicker` to `viewDidLoad`, but before calling `setUpTextFieldPicker` (check the updated answer if you are not sure what I mean).. before the `datePicker` for `dateIssueTextField` was not the same as `self.datePicker` when the delegate callback was called – Milan Nosáľ Feb 21 '18 at 18:23
  • I did the change. It Works! Thank you! – Renan Aguiar Feb 21 '18 at 18:31