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://
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.
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];
}