I have a UITextField that has a target added which performs checks on the field as the user is typing. I currently have an issue however when my code adds text to the textfield in that the text doesn't get checked. Is there a way I can solve this through .editingChanged or is there another UIControlEvent to hook into?
Code is:
NumberOutlet.addTarget(self, action: #selector(handleNumberImage), for: .editingChanged)
The way you can handle this is by implementing the UITextViewDelegate protocol in your viewcontroller. In your viewDidLoad you would want to set the delegate of your UITextField to self.
Then, simply implement the following method, like demonstrated here:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.length > 1 {
// Text was pasted into the text field
// do something with pasted text
} else {
//typed string
}
return true
}
You will want to conform to the UITextFieldDelegate protocol.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let isAddingCharacter: Bool = range.location >= 0 && range.length == 0
let isDeletingCharacter: Bool = range.location >= 0 && range.length == 1
var newCount: Int = textView.text.characters.count
if isAddingCharacter {
newCount += 1
} else if isDeletingCharacter {
newCount -= 1
}
// If the newCount is > 0, the user is entering text
return true
}
Side note, your outlet should be named numberOutlet, not NumberOutlet. It is convention to use camel case syntax for variable names in swift.
The only way I know would be just to call the method you used as selector after you add text via code.
For example you have a method which is executed after you press a button and there you add text to your textfield and the UIControlEvent doesn't get fired here. So just call the method after adding text via code in this example after pressing a button:
#IBAction func buttonPressed(_ sender: UIButton) {
// Note: camel case convention
numberOutlet.text?.append("added text via code")
// perform your check method
handleNumberImage()
}
Related
I'm implementing a tagging feature similar to that of Facebook. So when I type # and some character(s) after it, the function should return the word being typed.
So if the textView contains (and the cursor is at c)
Hello #Jac !
The function should return "#Jac"
If it contains (and the cursor is at a)
Hello #Ja !
Then the function should return "#Ja"
The final string of both examples would be,
Hello Jack !
I have attempted multiple solutions but none are working. One particular question was very similar to my question, but the solution has errors. Here is the link.
Update 1
Here is how I've set the delegate on the textView,
postView.textView.delegate = self
This is the code for detecting if the # character was tapped (display the friends list table, if it was)
if let text = self?.characterBeforeCursor() {
if (text == "#" && self?.friends.count != 0) {
self?.friendTableView.isHidden = false
} else {
var word // Need to get the word being typed
self?.displayedFriends = (self?.displayedFriends.filter { ($0["firstName"]?.hasPrefix(word))! })!
}
}
Update 2
The solution below did not solve the problem. It is returning all text in the textfield instead of just the word that is being typed.
There is a delegate function called shouldChangeCharactersInRange. From there you can get the current text after the user tapped the letter.
Make sure you use UITextFieldDelegate in your class declaration and set the textField's delegate to self.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//get the updated text from the text field like this:
let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
//note that we *need* to use the text as NSString, because the delegate method gives us an NSRange, rather than a Range which we can't use on String, but NSString, so we need to convert that first
return true //so the text is visually updated in the textfield
}
Edit
I just saw that you posted about UITextView. It's pretty much the same:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let text = (textView.text as NSString?)?.replacingCharacters(in: range, with: text)
return true
}
Edit 2
You need to also assign the text view's text to your variable in the code snippet you provided:
} else {
var word = postView.textView.text
self?.displayedFriends = (self?.displayedFriends.filter { ($0["firstName"]?.hasPrefix(word))! })!
}
How would I make it so a button that uses the segue to send you to the next view controller not work if nothing is entered in the text field above it?
Add this to the viewdidload, replacing textfieldVerb with the name of your textbox, and nextVerbOutlet with the name of your button (as a outlet)
self.textFieldVerb.addTarget(self, action: "textFieldChanged:", forControlEvents: .EditingChanged)
self.textFieldVerb.addTarget(self, action: "textFieldChanged:", forControlEvents: .EditingChanged)
nextVerbOutlet.enabled = false
and then add this replacing textfieldVerb with the name of your textbox, and nextVerbOutlet with the name of your button (as a outlet). This doesn't go in the viewdidload, but under it.
func textFieldChanged(sender: UITextField) {
// simple validation
if textFieldVerb.text?.characters.count > 0
&& textFieldVerb.text?.characters.count > 0 {
self.nextVerbOutlet.enabled = true // re-enable your button
}
}
You could implement optional func textField(_ textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool of the UITextFieldDelegate. It will get triggered every time user enters or deletes character in the text field. You can examine the contents there and enable/disable your button from there appropriately.
You could check for the content of the TextField:
if myTF.text != "" || myTF.text != nil {
//TextField contains something
//enable button
//segue to next ViewController
} else {
//TextField empty
//disable button
}
If you implement that into textFieldDidEndEditing() it will check each time the user is done typing.
Make sure to include the UITextFieldDelegate in your class like:
class myClass: UIViewController, UITextFieldDelegate {...}
Hope that helps :)
Implement the UITextFieldDelegate protocol and set your VC as the delegate.
Then implement the
func textField(_ textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String)
method of that protocol.
You use this method to detect an empty string. You can choose to be thorough and handle copy/paste in which case you will need to calculate the value of the new string after the replacement using stringByReplacingCharactersInRange, or you can choose to not handle copy paste and just check that range.location > 0.
Based upon the the above you can set the enabled property of the button accordingly.
So I have this so far
{
nextNounOutlet.enabled = false
super.viewDidLoad()
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textfieldNoun.text != "" {
nextNounOutlet.enabled = false
}
return true
}
I want to make it so that the nextNounOutlet button can't be clicked until something has been put into the text box, but it won't work. This way, it will stay disable forever. I've tried adding else statements, but that won't work and changing the true and false around won't work either.
Any suggestions, i'm using swift.
You need to modify the code so that it is not a "one way street": currently, once shouldChangeCharactersInRange method disables the button, there is nothing in your code to re-enable it.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Get the text after applying the update
var txtAfterUpdate:NSString = self.textfieldNoun.text as NSString
txtAfterUpdate = txtAfterUpdate.stringByReplacingCharactersInRange(range, withString: string)
// Enable or disable the button based on the length of the updated text
nextNounOutlet.enabled = (txtAfterUpdate.length != 0)
}
#dasblinkenlight's answer should work for you, if it doesn't you can re-approach the problem programmatically.
In ViewDidLoad you can add targets to your textFields:
self.firstNameField.addTarget(self, action: "textFieldChanged:", forControlEvents: .EditingChanged)
self.lastNameField.addTarget(self, action: "textFieldChanged:", forControlEvents: .EditingChanged)
Then create an action:
func textFieldChanged(sender: UITextField) {
// simple validation
if firstNameField.text?.characters.count > 0
&& lastNameField.text?.characters.count > 0 {
self.createButton.enabled = true // re-enable your button
}
}
I want to limit the number of characters a user can use in a textField. I took a function from this link: Max length UITextField (Imanou Petit)
However, in my viewDidLoad() I have several textFields that I'm already referencing the delegate because I want the keyboard to "Return" when the user presses the Return key on the keyboard. This I'm doing through the textFieldShouldReturn like this (I also have a touchesBegan method but I want the user to also have the option of the Return key):
func textFieldShouldReturn(textField: UITextField!) -> Bool {
self.stuffOneTextField.resignFirstResponder()
self.linkTextField.resignFirstResponder()
self.descriptionTextField.resignFirstResponder()
self.ogTextField.resignFirstResponder()
self.priceTextField.resignFirstResponder()
return true
}
If I add in this function below to the viewDidLoad, then the 'Return' key on the keyboard doesn't work and it limits ALL of the textFields (I have 5).
override func viewDidLoad() {
super.viewDidLoad()
self.stuffOneTextField.delegate = self
self.linkTextField.delegate = self
self.descriptionTextField.delegate = self
self.ogTextField.delegate = self
self.priceTextField.delegate = self
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limitLength
}
I only need to limit 2 textFields. I've tried just putting in the specific textField name instead of all the textFields as textFields and then it limits 1 textField and doesn't let me type in the others... Very strange...
How do I go around this?
Any help means a lot.
You're being given the textField that is changing characters in the delegate function. Here, you can compare it to the specific fields that you want to limit:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
if !(textField == stuffOneTextField || textField == descriptionTextField) {
return true
}
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limitLength
}
Also, func textFieldShouldReturn(textField: UITextField!) -> Bool is giving you a text field, and you can call resignFirstResponder() on that, if that is what you want to do there.
I'm having a problem regarding the creation of a prefix inside a UITextField using the new Swift language. Currently I have created the UITextField using the Interface Builder and I have assigned an IBOutlet to it, named usernameField, then using the textFieldDidBeginEditing function I write a NSMutableAttributedString inside it, named usernamePrefix, containing only the word "C-TAD-" and finally I limited the UITextField max characters number to 13, like so:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var usernameField : UITextField!
private var usernamePrefix = NSMutableAttributedString(string: "C-TAD-")
func textFieldDidBeginEditing(textField: UITextField) {
if textField == usernameField {
if usernameField.text == "" {
usernameField.attributedText = usernamePrefix
}
}
usernameField.addTarget(self, action: "textFieldDidChangeText:", forControlEvents:UIControlEvents.EditingChanged)
}
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
let maxUsernameLength = countElements(usernameField.text!) + countElements(string!) - range.length
return maxUsernameLength <= 13
}
override func viewDidLoad() {
super.viewDidLoad()
usernameField.delegate = self
passwordField.delegate = self
}
}
Now, how can I assign new parameters to the usernamePrefix in order to have to give 2 different colors to the text written in the UITextField? I would like to have the prefix in .lightGreyColor() and the rest in .blackColor(). Also how can I make the usernamePrefix un-editable and un-deletable by the user?
Thanks for the help
Simpler option would be to set leftView of the UITextField and customise it how you like it:
let prefix = UILabel()
prefix.text = "C-TAD-"
// set font, color etc.
prefix.sizeToFit()
usernameField.leftView = prefix
usernameField.leftViewMode = .whileEditing // or .always
It is un-editable and un-deletable and you don't need to do any calculations to check the length of the input.
For the first part, you can refactor your delegate method as follow.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//This makes the new text black.
textField.typingAttributes = [NSForegroundColorAttributeName:UIColor.blackColor()]
let protectedRange = NSMakeRange(0, 6)
let intersection = NSIntersectionRange(protectedRange, range)
if intersection.length > 0 {
return false
}
if range.location == 12 {
return true
}
if range.location + range.length > 12 {
return false
}
return true
}
This will lock down both the length at 13 and the prefix can not be deleted. Everything typed will be UIColor.blackColor()
Then you can a method like the following in your viewDidLoad, to set the prefix.
func makePrefix() {
let attributedString = NSMutableAttributedString(string: "C-TAD-")
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.lightGrayColor(), range: NSMakeRange(0,6))
textField.attributedText = attributedString
}
I've adopted the solution from Jeremy and make a little bit improvement to make it a bit more swifty, and also handle the case when user pastes multiple characters into the text field.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let protectedRange = NSRange(location: 0, length: usernamePrefix.length)
let intersection = protectedRange.intersection(range)
// prevent deleting prefix
if intersection != nil {
return false
}
// limit max character count
if (textField.text ?? "").count + string.count > 13 {
return false
}
return true
}