UIBUTTON and picker view

前端 未结 3 1202
一整个雨季
一整个雨季 2021-01-20 17:28

I want to display a picker when the user clicks on a button I subclassed the UIButton and changed its inputview so that it is writable.

I followed this link http://

3条回答
  •  伪装坚强ぢ
    2021-01-20 17:53

    This is how I did it in Swift 3 (the rules for overriding properties are different from Objective-C).

    I wanted a button that displays a date, and to display a date picker from the bottom of the screen on button tap, to allow the user to edit the date displayed. This is relatively easy to do with a popover anchored over the target button, but my app is iPhone-only (not iPad) so the UITextField-inputView pattern seems better (use on AppStore apps at your own risk of violating the Human Interface Guidelines).

    Button subclass:

    class EditableButton: UIButton {
    
        // This holds the assigned input view (superclass property is readonly)
        private var customInputView: UIView?
    
        // We override the superclass property as computed, and actually 
        // store into customInputView: 
        override var inputView: UIView? {
            get {
                return customInputView
            }
            set (newValue) {
                customInputView = newValue
            }
        }
    
        // Must be able to become first responder in order to 
        // get input view to be displayed:
        override var canBecomeFirstResponder: Bool {
            return true
        }
    
        // Setup becoming first responder on tap:
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            self.addTarget(self, action: #selector(EditableButton.tap(_:)), for: .touchDown)
            // (Not sure if this creates a retain cycle; must investigate)
        }
    
        @IBAction func tap(_ sender: UIButton) {
            self.becomeFirstResponder()
        }
    } 
    

    View Controller Code:

    class ViewController: UIViewController {
    
        // Set the appropriate subclass in the storyboard too!
        @IBOutlet weak var todayButton: EditableButton!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
    
            let datePicker = UIDatePicker()
            datePicker.datePickerMode = .date
            todayButton.inputView = datePicker
    
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd"
            todayButton.setTitle(dateFormatter.string(from: Date()), for: .normal)
    
            // Also hook an a target/action to the date picker's 
            // valueChanged event, to get notified of the user's input
            datePicker.addTarget(self, action: #selector(ViewController.changeDate(_:)), for: .valueChanged)
        }
    
        @IBAction func changeDate(_ sender: UIDatePicker) {
            // (update button title with formatted date)
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd"
            todayButton.setTitle(dateFormatter.string(from: sender.date), for: .normal)  
    
            // Dismiss picker:
            todayButton.resignFirstResponder()
        }
    }
    

    Note: For simplicity, this code will dismiss the date picker as soon a date is specified -i.e., whenever you scroll any of the wheels. This might not be what you want: Typically, the user specifies a date one component at a time (year, month, etc.). This implementation won't let the user finish specifying all date components before dismissing the date picker.

    Ideally, you would add a UIToolbar as an input accessory view, with a "done" button that dismisses the input view (i.e., causes the button to resign first responder). That would require further modification of the UIButton subclass (to also accommodate an inputAccessoryView), but should be doable.


    EDIT: I can confirm that an input accessory view can also be attached to the custom UIButton subclass in the exact same way as the input view:

    private var customInputAccessoryView: UIView?
    
    override var inputAccessoryView: UIView? {
        get {
            return customInputAccessoryView
        }
        set (newValue) {
            customInputAccessoryView = newValue
        }
    }
    

    Now, I set it up like this:

    let datePicker = UIDatePicker()
    datePicker.datePickerMode = .date
    todayButton.inputView = datePicker
    
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd"
    todayButton.setTitle(dateFormatter.string(from: Date()), for: .normal)
    
    datePicker.addTarget(self, action: #selector(ViewController.changeDate(_:)), for: .valueChanged)
    
    let dateToolbar = UIToolbar(frame: CGRect.zero)
    dateToolbar.barStyle = .default
    
    let dateDoneButton = UIBarButtonItem(
            barButtonSystemItem: .done,
            target: self,
            action: #selector(ViewController.dateDone(_:)))
    let dateSpacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
    
    dateToolbar.items = [dateSpacer, dateDoneButton]
    dateToolbar.sizeToFit()
    todayButton.inputAccessoryView = dateToolbar
    

    And respond with two separate actions:

    @IBAction func changeDate(_ sender: UIDatePicker) {
        // Update button title in real time:
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        todayButton.setTitle(dateFormatter.string(from: sender.date), for: .normal)
    }
    
    @IBAction func dateDone(_ sender: UIBarButtonItem) {
        // ...but dismiss picker only on 'Done': 
        todayButton.resignFirstResponder()
    }
    

    Also, the deinit() method of my custom button subclass does get called when the view controller is popped out of the navigation so either UIButton no longer (?) retains its targets, or the Swift runtime is smarter than I thought...


    UPDATE: I put together an Xcode project demonstrating this custom button (Github repository). It should be quite straightforward to modify it to work with a custom UIPickerView (instead of a UIDatePicker). What is not immediately obvious (to me, at least) is how to make it work with a keyboard as input view.

提交回复
热议问题