I have a UITextfield in a storyboard.
ClearButton is set to 'is always visible'
searchTextField.addTarget(self, action: #selector(searchTextFieldDidChange(textField:)), for: .editingChanged)
When the text field changes, this method is called
#objc func searchTextFieldDidChange(textField: UITextField){
if textField.text == "" {
textField.resignFirstResponder()
}
fireSearch()
}
When I clear the text field using backspace, textField.resignFirstResponder() is called, the keyboard vanishes as I want it.
When I clear the text field using the clear button, textField.resignFirstResponder() is called, the keyboard vanishes and appears again immediately.
What can I do that the keyboard keeps being closed when I tap the clear button?
Give this a try...
// conform to UITextFieldDelegate
class ViewController: UIViewController, UITextFieldDelegate {
// assuming this is created in Storyboard
#IBOutlet var searchTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
searchTextField.addTarget(self, action: #selector(searchTextFieldDidChange(textField:)), for: .editingChanged)
// set the delegate
searchTextField.delegate = self
}
#objc func searchTextFieldDidChange(textField: UITextField){
if textField.text == "" {
textField.resignFirstResponder()
}
fireSearch()
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
// clear the text
// Note: this does NOT fire searchTextFieldDidChange()
textField.text = ""
// resign
textField.resignFirstResponder()
// if you want to fire the search after text has been cleared
//fireSearch()
// return false to stop the default action of the clear button
return false
}
func fireSearch() {
print(#function)
}
}
so I have a login screen in my app. When the user clicks login, an api is called to check if the login credentials are correct (or not) and an error message is displayed when the login credentials are incorrect. Now the problem I'm having is after the login button is called and an error message is displayed, I want the error message to disappear when the user begins to change the text in the username/password field (UITextField). At first I tried using an event listener to try to set the alpha of the error message to 0.0 when the listener called a function. The code I used for that is down below.
textField.addTarget(self, action: #selector(textFieldDidChange(textfield:)), for: .editingChanged)
Then I had an objc function for that action:
#objc func textFieldDidChange(textfield: UITextField) {
errorLabel.alpha = 0.0
}
The problem I'm having with this is the error message disappears when the user only clicks on the textfield. I only want the error message to disappear if the user actually changes the text in the textfield. How should I go about this? Please let me know if you need anything else.
I wrote a quick version of the code
The solution is to create 2 variables which will do the work of storing the values of your credentials when they are wrong
import UIKit
class ViewController: UIViewController {
var usernameText:String? = nil
var passwordText:String? = nil
// we will use usernameText and PasswordText to keep track of the value of the textFields
let usernameField:UITextField = {
let textfield = UITextField()
textfield.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
return textfield
}()
let passwordField:UITextField = {
let textfield = UITextField()
textfield.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
return textfield
}()
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func loginButtonTouched(){
if we touch your login button and the credentials are wrong {
// you will show the errorLabel
ErrorLabel.alpha = 1
// and give value to usernameText and passwordText
usernameText = usernameField.text
passwordText = passwordField.text
}else{
present(nextVC) // for example
}
}
// the username and the password textField have the same action if the text change
#objc func textFieldDidChange(_ textfield: UITextField) {
// now we will use the values we gave to usernameText and passwordText to check if one of them has changed their value
if usernameText != usernameField.text || passwordText != passwordField.text {
// if one of them changed their value we make disappear the errorLabel
ErrorLabel.alpha = 0.0
}
}
}
I got problem while showing timer picker. There are two text fields: 1. first one opens a number pad. 2. second opens time picker from material controls.
Once I start editing first TF & if I tap on second TF the number pad is not getting dismissed even if I tap on Done button.
the done button is created using ToolBar & the method contains "view.endEditing" code. It works fine normally but not in above condition.
override func viewDidLoad() {
super.viewDidLoad()
rateInPointsTextField.delegate = self
}
extension MyAvailabilityVC : UITextFieldDelegate{
let toolbar = UIToolbar()
toolbar.sizeToFit()
//done button for toolbaar
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
toolbar.setItems([done], animated: false)
self.rateInPointsTextField.inputAccessoryView = toolbar
#objc func donePressed(){
self.view.endEditing(true)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.fromTimeTextField{
let timePicker = MDTimePickerDialog()
timePicker.delegate = self
timePicker.tag = 100
timePicker.clockMode = .mode12H
timePicker.show()
self.view.endEditing(true)
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.rateInPointsTextField.resignFirstResponder()
return true
}
}
Confirm UITextFieldDelegate in your view controller & set TF delegate to self
class YourViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var your_tf: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
your_tf.delegate = self;
}
....
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
Now when you will press done/return button of keyboard then keyboard will dismiss. If you want to dismiss from your created button then make call from done #Action fun to textFieldShouldReturn(your_tf) like this.
...
#objc func donePressed(){
textFieldShouldReturn(your_tf)
}
As homework, I need to make simple calculator to calculate installments. I can't find and method to connect a UISlider and a UITextField to synchronize both to choose correct data.
I tried it, but it works for slider to textfield :
#IBAction func change(_ sender: Any) {
TextField.text = String(Int(slider.value))
}
try use this, IBAction can be irritating at the beginning of learning
override viedDidLoad() {
super.viewDidLoad()
addTarget()
}
func addTarget() {
slider.addTarget(self, action: #selector(onSliderChange), for: UIControl.Event.valueChanged)
}
//callback called when value change
#objc func onSliderChange(){
textField.text = String(Int(slider.value))
}
I use the "Next" value for the "Return Key" to get the Next button in place of the Done button, but (obviously) pressing it doesn't automatically move to the next UITextField in my view.
What's the right way to do this? I have seen many answers, but anyone have a swift solution?
Make sure your text fields have their delegate set and implement the textFieldShouldReturn method. This is the method that is called when the user taps the return key (no matter what it looks like).
The method might look something like this:
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == self.field1 {
self.field2.becomeFirstResponder()
}
return true
}
The actual logic in here might vary. There are numerous approaches, and I'd definitely advise against a massive if/else chain if you have lots of text fields, but the gist here is to determine what view is currently active in order to determine what view should become active. Once you've determined which view should become active, call that view's becomeFirstResponder method.
For some code cleanliness, you might consider a UITextField extension that looks something like this:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
And then change our textFieldShouldReturn method to look like this:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.nextField?.becomeFirstResponder()
return true
}
Once you've done this, it should simply be a matter of setting each text field's new nextField property in viewDidLoad:
self.field1.nextField = self.field2
self.field2.nextField = self.field3
self.field3.nextField = self.field4
self.field4.nextField = self.field1
Although if we really wanted, we could prefix the property with #IBOutlet, and that would allow us to hook up our "nextField" property right in interface builder.
Change the extension to look like this:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
#IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
And now hook up the nextField property in interface builder:
(Set up your delegate while you're here too.)
And of course, if the nextField property returns nil, the keyboard just hides.
Here is an example in Swift:
I created a screen with 6 UITextFields. I assigned them the tags 1 through 6 in Interface Builder. I also changed the Return key to Next in IB. Then I implemented the following:
import UIKit
// Make your ViewController a UITextFieldDelegate
class ViewController: UIViewController, UITextFieldDelegate {
// Use a dictionary to define text field order 1 goes to 2, 2 goes to 3, etc.
let nextField = [1:2, 2:3, 3:4, 4:5, 5:6, 6:1]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Make ourselves the delegate of the text fields so that textFieldShouldReturn
// will be called when the user hits the Next/Return key
for i in 1...6 {
if let textField = self.view.viewWithTag(i) as? UITextField {
textField.delegate = self
}
}
}
// This is called when the user hits the Next/Return key
func textFieldShouldReturn(textField: UITextField) -> Bool {
// Consult our dictionary to find the next field
if let nextTag = nextField[textField.tag] {
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
// Have the next field become the first responder
nextResponder.becomeFirstResponder()
}
}
// Return false here to avoid Next/Return key doing anything
return false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
There is nothing wrong with the other answers, this is just a different approach with the benefit of being more focused on OOP - imho (although this is a bit more work up front, it can be reused). In the storyboard, I start off adding tags with a distinct range (e.g 800-810) that define the specific order of the fields I want to move between. This has the benefit of working across all subviews in the main view so that one can navigate between UITextField's and UITextView's (and any other control) as needed.
Generally - I typically try to have view controllers message between views and custom event handler objects. So I use a message (aka, NSNotification) passed back to the view controller from a custom delegate class.
(TextField Delegate Handler)
Note: In AppDelegate.swift: let defaultCenter = NSNotificationCenter.defaultCenter()
//Globally scoped
struct MNGTextFieldEvents {
static let NextButtonTappedForTextField = "MNGTextFieldHandler.NextButtonTappedForTextField"
}
class MNGTextFieldHandler: NSObject, UITextFieldDelegate {
var fields:[UITextField]? = []
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return true
}
func textFieldDidBeginEditing(textField: UITextField) {
textField.backgroundColor = UIColor.yellowColor()
}
func textFieldDidEndEditing(textField: UITextField) {
textField.backgroundColor = UIColor.whiteColor()
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldClear(textField: UITextField) -> Bool {
return false
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
//passes the message and the textField (with tag) calling the method
defaultCenter.postNotification(NSNotification(name: MNGTextFieldEvents.NextButtonTappedForTextField, object: textField))
return false
}
}
This allows my view controller to remain focused on it's main job of handling the messaging between objects, model and view.
(View Controller receives a message from the delegate and passes instructions using the advanceToNextField function)
Note: In my storyboard my custom handler classes are defined using an NSObject and that object is linked into the storyboard as a delegate for the controls that I need monitored. Which causes the custom handler class to be initialized automatically.
class MyViewController: UIViewController {
#IBOutlet weak var tagsField: UITextField! { didSet {
(tagsField.delegate as? MNGTextFieldHandler)!.fields?.append(tagsField)
}
}
#IBOutlet weak var titleField: UITextField!{ didSet {
(titleField.delegate as? MNGTextFieldHandler)!.fields?.append(titleField)
}
}
#IBOutlet weak var textView: UITextView! { didSet {
(textView.delegate as? MNGTextViewHandler)!.fields?.append(textView)
}
}
private struct Constants {
static let SelectorAdvanceToNextField = Selector("advanceToNextField:")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
registerEventObservers()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
deRegisterEventObservers()
}
func advanceToNextField(notification:NSNotification) {
let currentTag = (notification.object as! UIView).tag
for aView in self.view.subviews {
if aView.tag == currentTag + 1 {
aView.becomeFirstResponder()
}
}
}
func registerEventObservers () {
defaultCenter.addObserver(self, selector: Constants.SelectorAdvanceToNextField, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
}
func deRegisterEventObservers() {
defaultCenter.removeObserver(self, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
}
....
}
Just another way to achieve the result that I found helpful. My app had 11 text fields followed by a text view. I needed to be able to cycle through all fields using the next key and then resign the keyboard following the textview (i.e. other notes).
In the storyboard, I set the tag on all of the fields (both text and textview) starting with 1 through 12, 12 being the textview.
I'm sure there are other ways to do it and this method isn't perfect, but hopefully it helps someone.
In code, I wrote the following:
func textFieldShouldReturn(textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
//Handle Textview transition, Textfield programmatically
if textField.tag == 11 {
//Current tag is 11, next field is a textview
self.OtherNotes.becomeFirstResponder()
} else if nextTag > 11 {
//12 is the end, close keyboard
textField.resignFirstResponder()
} else {
//Between 1 and 11 cycle through using next button
let nextResponder = self.view.viewWithTag(nextTag) as? UITextField
nextResponder?.becomeFirstResponder()
}
return false
}
func textFieldDidEndEditing(textField: UITextField) {
textField.resignFirstResponder()
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
//Remove keyboard when clicking Done on keyboard
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
Another approach, if you're using storyboards, you can change the textfield's attribute for Return Key.
Currently you have the following options: Default (Return), Go, Google, Join, Next, Route, Search, Send, Yahoo, Done, Emergency Call, Continue