So I am using a UIPickerView as the first responder of a UITextField. I set this as shows:
var myPickerView = UIPickerView()
myTextField.inputView = myPickerView
I was not having issues with this until I added a UITapGestureRecognizer (that would allow me to click outside of the picker view to dismiss it) to the view like so:
let dismissUnitPickerGesture = UITapGestureRecognizer(target: self, action: "hidePicker")
dismissUnitPickerGesture.delegate = self
dismissUnitPickerGesture.cancelsTouchesInView = false
hidePicker func:
#IBAction func hidePicker(){
myTextField.resignFirstResponder()
//I don't think this code is relevant to the problem, but I included it just in case
if materialSelectedOption == "Aluminum Cans" {
amountTextField.placeholder = "Number of Cans"
unitCell.hidden = true
}
}
Now it takes up to five clicks on the UITextField on the simulator to open the picker view, which is frustrating and obviously not good for an app. I'm pretty sure it has something to do with the tap gesture, but I could be wrong.
If there are any other questions you guys have please let me know.
adding a UITapGestureRecognizer to just detect that a tap is registered outside the pickerView and hide it is kind of wrong!
you can use the touchesBegan function and call self.view.endEditing(true) like so:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
//or use textField.endEditing(true)
}
I Suggest you to add tool bar with "Done" and "Clear" items instead gesture.
The following code you can use:
func textFieldDidBeginEditing(sender: UITextField) {{
let toolBar = UIToolbar(frame: CGRectMake(0, self.myTextField.frame.size.height/6, self.myTextField.frame.size.width, 40.0))
toolBar.layer.position = CGPoint(x: self.myTextField.frame.size.width/2, y: self.myTextField.frame.size.height-20.0)
toolBar.barStyle = UIBarStyle.BlackTranslucent
toolBar.tintColor = UIColor.whiteColor()
let defaultButton = UIBarButtonItem(title: "Clear", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(CustomTimePickerTextView.clearTime(_:)))
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: #selector(CustomTimePickerTextView.donePressed(_:)))
let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let textButton = UIBarButtonItem(title: self.placeholder, style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
toolBar.setItems([defaultButton,flexSpace,textButton,flexSpace,doneButton], animated: true)
self.myTextField.inputAccessoryView = toolBar
}
func clearTime(sender:UIBarButtonItem) {
//
}
func donePressed(sender: UIBarButtonItem) {
self.myTextField.resignFirstResponder()
}
Related
I'm using two textField which is of numPad keyboard type. I have added Next button for the first textField and Done button for the second textField in the numPad keyboard. Using the following code.
import UIKit
class SecurityPinSettingsVC: UIViewController, UITextFieldDelegate
{
#IBOutlet weak var textField1: UITextField!
#IBOutlet weak var textField2: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
self.textField1.delegate = self
self.textField2.delegate = self
self.textField1.textAlignment = NSTextAlignment.center
self.textField2.textAlignment = NSTextAlignment.center
addNextButtonOnKeyboard()
addDoneButtonOnKeyboard()
}
func addNextButtonOnKeyboard()
{
let nextToolbar: UIToolbar = UIToolbar(frame: CGRect(x: 0,y: 0, width: 320, height: 50))
nextToolbar.barStyle = UIBarStyle.default
let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let next: UIBarButtonItem = UIBarButtonItem(title: "Next", style: UIBarButtonItemStyle.done, target: self, action: #selector(ViewController.doneButtonAction))
var items = [UIBarButtonItem]()
items.append(flexSpace)
items.append(next)
nextToolbar.items = items
nextToolbar.sizeToFit()
self.textField1.inputAccessoryView = nextToolbar
}
func addDoneButtonOnKeyboard()
{
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 320, height: 50))
doneToolbar.barStyle = UIBarStyle.default
let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let ddone: UIBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.done, target: self, action: #selector(ViewController.doneButtonAction))
var items = [UIBarButtonItem]()
items.append(flexSpace)
items.append(ddone)
doneToolbar.items = items
doneToolbar.sizeToFit()
self.textField2.inputAccessoryView = doneToolbar
}
func doneButtonAction()
{
//self.textField2.becomeFirstResponder()
//self.textField2.resignFirstResponder()
}
}
Both buttons are successfully added. The problem is, I could not create separate action methods for Next button and Done button. It accepts only doneButtonAction method for both buttons, which I have created and used in previous viewControllers. So, How can I get the textField id in the doneButtonAction method, in-order to know which button is clicked. So that, by clicking on Next button I want to make textField2 to become first responder, and by clicking Done button I want textField2 to resign first responder.
Like in ordinary textField, textFieldShouldReturn method will do this job easily, where the textField object will be passed to that function as an argument.
Here, how can we pass the textField object to the doneButtonAction method.
I'm using Xcode 8.2, Swift 3.0
Thanks in Advance.
you can get text field which are become responder
if textField1.isFirstResponder {
// textfield 1 is becomeResponder
}
I have looked at many examples of extensions, thought can't find this. I am simply trying to use extension to add a toolbar with buttons on top of keyboard in swift, but since extensions can't have stored values easily , is there a simple way to pass variables to the selector methods for each buttons target in extension ? For example, I want to pass data to save to database in done() when save is tapped.
import UIKit
import FirebaseDatabase
extension UIViewController: UITextViewDelegate {
func addToolBar(textField: UITextView, mode: Int, ref: String , pkg: [String:Any]){
//mode: detailmsges = 2 ; home = 1
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.tintColor = UIColor.gray
let doneButton = UIBarButtonItem(title: "Save", style: UIBarButtonItemStyle.done, target: self, action: #selector(donePressed))
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
if mode == 1 {
let cancelButton = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.plain, target: self, action: #selector(cancelPressed))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
} else if mode == 2 {
toolBar.setItems([spaceButton, doneButton], animated: false)
}
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
textField.delegate = self
textField.inputAccessoryView = toolBar
}
//how can I receive the parameters that I get in addToolBar() here to save to DB?
func donePressed(){
view.endEditing(true)
}
func cancelPressed(){
view.endEditing(true)
}
}
then I use it like:
addToolBar(textField: myTextField, mode: 1, ref: "", pkg: [:])
I have the following code and the doneButton handler is never invoked. Any idea what might be going wrong?
I have tried many different formats for the selector, also with and without parameters. I also made sure that the UIView received touch events by manually setting userInteractionEnabled = true. Thanks for any help in advance!
*** UPDATE: I have refactored the code completely, but same behaviour:
My Protocol:
import Foundation
protocol CustomDatePickerDelegate {
func dataPickerDone(date: NSDate)
func dataPickerChanged(date: NSDate)
}
my class:
import Foundation
import UIKit
class CustomDatePicker{
var delegate: CustomDatePickerDelegate?
var datePicker : UIDatePicker = UIDatePicker()
func CreateDatePicker(sender: UITextField) {
if (delegate is UIViewController)
{
let _self = delegate as! UIViewController
let _view = UIView(frame: CGRectMake(0, 0, _self.view.frame.width, 220))
let datePickerView : UIDatePicker = UIDatePicker(frame: CGRectMake(0, 40, 0, 0))
datePickerView.datePickerMode = UIDatePickerMode.Date
let _toolbar = UIToolbar()
let _doneButton = UIBarButtonItem(title: "DONE", style: UIBarButtonItemStyle.Done, target: self, action: #selector(CustomDatePicker.doneButton))
_toolbar.translucent = false
_toolbar.sizeToFit()
_doneButton.accessibilityLabel = "DoneToolbar"
let _spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
_toolbar.setItems([_spaceButton, _doneButton], animated: false)
_toolbar.userInteractionEnabled = true
datePickerView.addTarget(self, action: #selector(CustomDatePicker.handleDatePicker), forControlEvents: UIControlEvents.ValueChanged)
_view.addSubview(datePickerView)
_view.addSubview(_toolbar)
sender.inputView = _view
}
}
#objc func handleDatePicker(sender:UIDatePicker) {
delegate?.dataPickerChanged(datePicker.date)
}
#objc func doneButton(){
delegate?.dataPickerDone(datePicker.date)
}
}
How I use it from the ViewController:
let _dataPicker : CustomDatePicker = CustomDatePicker()
_dataPicker.delegate = self
_dataPicker.CreateDatePicker(dataUI)
func dataPickerDone(date: NSDate) {
dataUI.text = DataFormatejada(date)
dataUI.resignFirstResponder()
}
func dataPickerChanged(date: NSDate) {
dataUI.text = DataFormatejada(date)
}
I found a way to resolve the problem after many hours of try-error. The problem is with the target:self reference in the lines:
let _doneButton = UIBarButtonItem(title: "DONE", style: UIBarButtonItemStyle.Done, target: self, action: #selector(CustomDatePicker.doneButton))
and
_toolbar.userInteractionEnabled = true
datePickerView.addTarget(self, action: #selector(CustomDatePicker.handleDatePicker), forControlEvents: UIControlEvents.ValueChanged)
so what I have done is to create a new property in MyDatePicker class:
var instance : CustomDatePicker?
change those addTarget methods as:
let _doneButton = UIBarButtonItem(title: "DONE", style: UIBarButtonItemStyle.Done, target: instance, action: #selector(CustomDatePicker.doneButton))
_toolbar.userInteractionEnabled = true
datePickerView.addTarget(instance, action: #selector(CustomDatePicker.handleDatePicker), forControlEvents: UIControlEvents.ValueChanged)
and I use it from the ViewController as:
let _dataPicker : CustomDatePicker = CustomDatePicker()
_dataPicker.delegate = self <---- **this made the trick**
_dataPicker.instance = _dataPicker
_dataPicker.CreateDatePicker(dataUI)
but this is not elegant and it is a patch for a more fundamental issue. Why self is not providing the instance of the object and I have to manually pass it back when I create the object?
thanks!
I had created button in toolbar and set tag to it. I had declared that button locally. Now in some other function I want to disable the button as per condition.
Is there any way to disable UIBarButton based on tag without declaring the button globally.
****************************************************
func setToolBar()
{
let toolbar : UIToolbar = UIToolbar()
toolbar.sizeToFit()
let prevButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action:"goBack:")
prevButton.tag = 20
let nextButton = UIBarButtonItem(title: ">", style: .Plain, target: self, action:"goNext:")
nextButton.tag = 30
let flexButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action:"done:")
let arrItems : NSArray = [prevButton , nextButton,flexButton, doneButton]
[toolbar.setItems(arrItems as? [UIBarButtonItem], animated: true)]
city.inputAccessoryView = toolbar
birthdate.inputAccessoryView = toolbar
}
****************************************************
func goBack(sender: UIBarButtonItem)
{
let prevTag : NSInteger = CurrentTextFiled.tag - 1
let nextResponder = CurrentTextFiled.superview?.viewWithTag(prevTag)
if((nextResponder) != nil)
{
nextResponder!.becomeFirstResponder()
}
else
{
//Disable UIBarButton here with tag 20
}
}
****************************************************
func goNext(sender: UIBarButtonItem)
{
let prevTag : NSInteger = CurrentTextFiled.tag + 1
let nextResponder = CurrentTextFiled.superview?.viewWithTag(prevTag)
if((nextResponder) != nil)
{
nextResponder!.becomeFirstResponder()
}
else
{
//Disable UIBarButton here with tag 30
}
}
I distilled this down to a code snippet that can be run inside a playground, and verified this within Xcode.
In essence, you want to give a tag to the toolbar and find it with viewWithTag(). Once you have the toolbar, traverse thru its items array and filter out the button you want to manipulate. Don't forget to add the toolbar to your viewController's view as a subview.
import UIKit
import XCPlayground
class MyViewController: UIViewController {
override func viewDidLoad() {
self.setToolBar()
self.disableButton(20)
}
func setToolBar()
{
let toolbar : UIToolbar = UIToolbar()
toolbar.tag = 10
toolbar.sizeToFit()
self.view.addSubview(toolbar) //must add to subview of viewcontroller
let prevButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action:"goBack:")
prevButton.tag = 20
let nextButton = UIBarButtonItem(title: ">", style: .Plain, target: self, action:"goNext:")
nextButton.tag = 30
let flexButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action:"done:")
let arrItems : NSArray = [prevButton, nextButton,flexButton, doneButton]
[toolbar.setItems(arrItems as? [UIBarButtonItem], animated: true)]
print("Toolbar set.")
}
func disableButton(tag: Int)
{
if let toolbarWithButtons = self.view.viewWithTag(10) as? UIToolbar {
print("Toolbar found by tag. Trying to disable button with tag \(tag).")
var buttonToDisable: Array<AnyObject>?
if let buttons = toolbarWithButtons.items {
buttonToDisable = buttons.filter({
(x : AnyObject) -> Bool in
if let button = x as? UIBarButtonItem {
if button.tag == tag {
return true
}
}
return false
})
if let button = (buttonToDisable!.first as? UIBarButtonItem){
button.enabled = false
print("Button with tag \(button.tag) enabled: \(button.enabled)")
}
}
}
else {
print("Toolbar not found by tag.")
}
}
}
var ctrl = MyViewController()
XCPShowView("Playground VC", ctrl.view)
I hope this is helpful!
You can get view a using tag.
if let button = self.view.viewWithTag(YOUR_TAG_HERE) as? UIBarButtonItem {
button.enabled = false
}
Instances of UIView have a method to find sub-views based on the tag:
func viewWithTag(_ tag: Int) -> UIView?
I'm pretty sure this will search deeply, so, as long as you can get to a view near the top of the hierarchy, this should work for you.
If some one still need this:
In case where you will have navigationController, so you can access to your UIBarButtonItem like so
if let barButtonItem = navigationItem.rightBarButtonItems?.first(where: {$0.tag == <YOUR_TAG> }) {
// Do staff here
}
and in case with toolBar situation is similar:
if let barButtonItem = toolBar.items?.first(where: {$0.tag == <YOUR_TAG>}) {
// Do staff here
}
So I have an view controller with a form like so:
When I use the back and forward buttons on the input accessory view, everything works fine. However, in simulator when I use the tab key on the keyboard to traverse the text fields, and then segue back to the previous view controller, I get the following:
and then this in the console:
I look for zombie objects in Instruments and find this:
but that doesn't provide much help. Any idea why this would ONLY happen when tab is pressed on the keyboard in simulator? It isn't an issue on the device and I can't reproduce it there but I feel like a zombie object needs to be address either way.
and here is my code for that view controller:
in viewDidLoad i'm calling setupInputAccessoryView
func setupInputAccessoryView(){
// Create a button bar for the number pad
let keyboardDoneButtonView = UIToolbar()
keyboardDoneButtonView.sizeToFit()
// Setup the buttons to be put in the toolbar.
let item4 = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("endEditingNow") )
let item = UIBarButtonItem(title: " < ", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("previousField"))
let item2 = UIBarButtonItem(title: " >", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("nextField"))
let item3 = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
if let font = UIFont(name: "Helvetica", size: 17) {
item4.setTitleTextAttributes([NSFontAttributeName: font], forState: UIControlState.Normal)
}
if let font2 = UIFont(name: "Helvetica", size: 26){
item.setTitleTextAttributes([NSFontAttributeName: font2], forState: UIControlState.Normal)
item2.setTitleTextAttributes([NSFontAttributeName: font2], forState: UIControlState.Normal)
}
//Add buttons to toolbar and add as an inputAccessoryView
var toolbarButtons = [item, item2, item3, item4]
keyboardDoneButtonView.setItems(toolbarButtons, animated: false)
keyBoardToolBar = keyboardDoneButtonView
}
and then in textFieldDidBeginEditing I have:
func textFieldDidBeginEditing(textField: UITextField) {
currentTextField = textField
textField.inputAccessoryView = keyBoardToolBar
if (textField == stateField){
statePicker.frame = CGRectZero
statePicker.delegate = self
statePicker.dataSource = self
textField.inputView = statePicker
}else if (textField == countryField){
countryPicker.frame = CGRectZero
countryPicker.delegate = self
countryPicker.dataSource = self
textField.inputView = countryPicker
}
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.scrollView.contentOffset.y = textField.center.y - 30
})
}
and then my selectors for the toolbar buttons
func endEditingNow(){
self.view.endEditing(true)
}
func nextField(){
var nextTag = currentTextField!.tag + 1
self.view.viewWithTag(nextTag)?.becomeFirstResponder()
}
func previousField(){
var nextTag = currentTextField!.tag - 1
self.view.viewWithTag(nextTag)?.becomeFirstResponder()
}