UIBUTTON and picker view

99封情书 提交于 2019-12-01 20:53:30

The inputView property is only the part of UITextField and UITextView not UIButton.

   @property (readwrite, retain) UIView *inputView;

Although you could achieve the same using UIButton.

Create an UIButton object then set an action method and add your picker view in parent view of your button in the action method of UIButton.

Should be something like that.

-(void) buttonClicked:(id) sender
{
    [self.view addSubview:myPickerView];
}

1) Create a subclass of a UIButton

.h

#import <UIKit/UIKit.h>

@interface UIButtonAsFirstResponder : UIButton

@property (strong, nonatomic) UIView *inputView;
@property (strong, nonatomic) UIView *inputAccessoryView;

@end

.m

#import "UIButtonAsFirstResponder.h"

@implementation UIButtonAsFirstResponder

- (BOOL) canBecomeFirstResponder
{
    return YES;
}

@end

2) Set inputView (if needed also inputAccessoryView) in your view controller (to your picker or any other custom view)

3) Create IBAction for your button in your view controller

- (IBAction)becom:(id)sender {
    [sender becomeFirstResponder];
}

If you want to remove keyboard, call on your button:

[button resignFirstResponder];

or in view controller:

[self.view endEditing:YES];

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!