Is there any way to detect when the Backspace/Delete key is pressed in the iPhone keyboard on a UITextField that is empty? I want to know when Backspace is pressed only if the UITextField is empty.
Based on the suggestion from #Alex Reynolds in a comment, I've added the following code while creating my text field:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleTextFieldChanged:)
name:UITextFieldTextDidChangeNotification
object:searchTextField];
This notification is received (handleTextFieldChanged function is called), but still not when I press the Backspace key in an empty field. Any ideas?
There seems to be some confusion around this question. I want to receive a notification when the Backspace key is pressed. That's it. But the solution must also work when the UITextField is already empty.
Swift 4:
Subclass UITextField:
// MyTextField.swift
import UIKit
protocol MyTextFieldDelegate: AnyObject {
func textFieldDidDelete()
}
class MyTextField: UITextField {
weak var myDelegate: MyTextFieldDelegate?
override func deleteBackward() {
super.deleteBackward()
myDelegate?.textFieldDidDelete()
}
}
Implementation:
// ViewController.swift
import UIKit
class ViewController: UIViewController, MyTextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// initialize textField
let input = MyTextField(frame: CGRect(x: 50, y: 50, width: 150, height: 40))
// set viewController as "myDelegate"
input.myDelegate = self
// add textField to view
view.addSubview(input)
// focus the text field
input.becomeFirstResponder()
}
func textFieldDidDelete() {
print("delete")
}
}
Objective-C:
Subclass UITextField:
//Header
//MyTextField.h
//create delegate protocol
#protocol MyTextFieldDelegate <NSObject>
#optional
- (void)textFieldDidDelete;
#end
#interface MyTextField : UITextField<UIKeyInput>
//create "myDelegate"
#property (nonatomic, assign) id<MyTextFieldDelegate> myDelegate;
#end
//Implementation
#import "MyTextField.h"
#implementation MyTextField
- (void)deleteBackward {
[super deleteBackward];
if ([_myDelegate respondsToSelector:#selector(textFieldDidDelete)]){
[_myDelegate textFieldDidDelete];
}
}
#end
Now simply add MyTextFieldDelegate to your UIViewController and set your UITextFields myDelegate to self:
//View Controller Header
#import "MyTextField.h"
//add "MyTextFieldDelegate" to you view controller
#interface ViewController : UIViewController <MyTextFieldDelegate>
#end
//View Controller Implementation
- (void)viewDidLoad {
//initialize your text field
MyTextField *input =
[[MyTextField alloc] initWithFrame:CGRectMake(0, 0, 70, 30)];
//set your view controller as "myDelegate"
input.myDelegate = self;
//add your text field to the view
[self.view addSubview:input];
}
//MyTextField Delegate
- (void)textFieldDidDelete {
NSLog(#"delete");
}
Update: See JacobCaraballo's answer for an example that overrides -[UITextField deleteBackward].
Check out UITextInput, specifically UIKeyInput has a deleteBackward delegate method that always gets called when the delete key is pressed. If you're doing something simple, then you might consider just subclassing UILabel and making it conform to the UIKeyInput protocol, as done by SimpleTextInput and this iPhone UIKeyInput Example. Note: UITextInput and its relatives (including UIKeyInput) are only available in iOS 3.2 and later.
This may be a long shot but it could work. Try setting the text field's text to a zero width space character \u200B. When backspace is pressed on a text field that appears empty, it will actually delete your space. Then you can just reinsert the space.
May not work if the user manages to move the caret to the left of the space.
Code like following:
#interface MyTextField : UITextField
#end
#implementation MyTextField
- (void)deleteBackward
{
[super deleteBackward];
//At here, you can handle backspace key pressed event even the text field is empty
}
#end
At last, do forget to change the Custom Class property of the Text Field to "MyTextField"
Swift implementation:
import UIKit
// Extend from PinTextFieldDelegate instead of UITextFieldDelegate in your class
protocol PinTextFieldDelegate : UITextFieldDelegate {
func didPressBackspace(_ textField: PinTextField)
}
class PinTextField: UITextField {
override func deleteBackward() {
super.deleteBackward()
// If conforming to our extension protocol
if let pinDelegate = self.delegate as? PinTextFieldDelegate {
pinDelegate.didPressBackspace(self)
}
}
}
I've founded other way easier than subclass solution. Even its little bit strange but it works ok.
- (BOOL)textView:(UITextView *)textView
shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
const char * _char = [text cStringUsingEncoding:NSUTF8StringEncoding];
int isBackSpace = strcmp(_char, "\b");
if (isBackSpace == -8) {
// is backspace
}
return YES;
}
It's a little bit strange for result of compare is -8. Maybe I'll wrong in some point of C Programming. But its right work ;)
please use below code it will help you to detect keyboard delete key even if you textfield is empty.
Objective C :
- (BOOL)keyboardInputShouldDelete:(UITextField *)textField { return YES; }
Swift :
func keyboardInputShouldDelete(_ textField: UITextField) -> Bool { return true }
Try the delegate
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
Then check if the range.length == 1 which seems to be the case when backspace is hit.
Niklas Alvaeus's answer helped me out with a similar issue
I was limiting entry to a specific character set, but it was ignoring backspaces.
So I had it check range.length == 1 before trimming the NSString. If it is true, I just return the string and don't trim it. See below
- (BOOL) textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
NSCharacterSet *nonNumberSet =
[[NSCharacterSet characterSetWithCharactersInString:#"0123456789."]
invertedSet];
if (range.length == 1) {
return string;
}
else {
return ([string stringByTrimmingCharactersInSet:nonNumberSet].length > 0);
}
}
Yup, use below method to detect backspace, when textField is empty.
Need to add UITextFieldDelegate
yourTextField.delegate = self (MUST REQUIRED)
Swift:
func keyboardInputShouldDelete(_ textField: UITextField) -> Bool {
return true
}
Objective C:
- (BOOL)keyboardInputShouldDelete:(UITextField *)textField {
return YES;
}
For the ones who has problems about the Jacob's answer I implemented my textfield subclass as following and it works great!
#import <UIKit/UIKit.h>
#class HTTextField;
#protocol HTBackspaceDelegate <NSObject>
#optional
- (void)textFieldDidBackspace:(HTTextField*)textField;
#end
#interface HTTextField : UITextField<UIKeyInput>
#property (nonatomic, assign) id<HTBackspaceDelegate> backspaceDelegate;
#end
#import "HTTextField.h"
#implementation HTTextField
- (void)deleteBackward {
[super deleteBackward];
if ([self.backspaceDelegate respondsToSelector:#selector(textFieldDidBackspace:)]){
[self.backspaceDelegate textFieldDidBackspace:self];
}
}
- (BOOL)keyboardInputShouldDelete:(UITextField *)textField {
BOOL shouldDelete = YES;
if ([UITextField instancesRespondToSelector:_cmd]) {
BOOL (*keyboardInputShouldDelete)(id, SEL, UITextField *) = (BOOL (*)(id, SEL, UITextField *))[UITextField instanceMethodForSelector:_cmd];
if (keyboardInputShouldDelete) {
shouldDelete = keyboardInputShouldDelete(self, _cmd, textField);
}
}
if (![textField.text length] && [[[UIDevice currentDevice] systemVersion] intValue] >= 8) {
[self deleteBackward];
}
return shouldDelete;
}
#end
The best use that I have found for detecting backspace is detecting when the user has pressed backspace in an empty UITextField. For example, if you have 'bubbled' recipients in the mail app, when you hit backspace in the UITextField, it selects the last 'bubbled' recipient.
This can be done in a similar way to Jacob Caraballo's answer. But in Jacob's answer, if the UITextField has one character left when you hit backspace, by the time the delegate message is received, the UITextField will already be empty, so you're effectively detecting backspace on a text field with at most one characters.
Actually, if you want to detect backspace on a UITextField with exactly zero characters (empty), then you should send the message to the delegate before the call to super deleteBackward. For example:
#import "MyTextField.h"
//Text field that detects when backspace is hit with empty text
#implementation MyTextField
#pragma mark - UIKeyInput protocol
-(void)deleteBackward
{
BOOL isTextFieldEmpty = (self.text.length == 0);
if (isTextFieldEmpty) {
if ([self.delegate
respondsToSelector:#selector(textFieldDidHitBackspaceWithEmptyText:)]) {
[self.delegate textFieldDidHitBackspaceWithEmptyText:self];
}
}
[super deleteBackward];
}
#end
The interface for such a text field would look something like this:
#protocol MyTextFieldDelegate;
#interface MyTextField : UITextField
#property(nonatomic, weak) id<MyTextFieldDelegate> delegate;
#end
#protocol MyTextFieldDelegate <UITextFieldDelegate>
#optional
-(void)textFieldDidHitBackspaceWithEmptyText:(MyTextField *)textField;
#end
In iOS 6, the deleteBackward method is called on the UITextField when backspace is pressed, including when the field is empty. So you can subclass UITextField and provide your own deleteBackward implementation (invoking super's as well.)
I'm still supporting iOS 5 though so I'll need a combination of Andrew's answer and this.
In .h file add UIKeyInput delegate
- (BOOL)keyboardInputShouldDelete:(UITextField *)textField {
if ([textField isEqual:_txtFirstDigit]) {
}else if([textField isEqual:_txtSecondDigit]) {
[_txtFirstDigit becomeFirstResponder];
}else if([textField isEqual:_txtThirdDigit]) {
[_txtSecondDigit becomeFirstResponder];
}else if([textField isEqual:_txtFourthDigit]) {
[_txtThirdDigit becomeFirstResponder];
}
return YES;
}
improved Formatting
:) just for the title "Detect backspace", where I use UIKeyboardTypeNumberPad.
I also meet the same question tonight, and following is my code to find it out:
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
NSLog([NSString stringWithFormat:#"%d", [string length]]);
}
Because with UIKeyboardTypeNumberPad, user can only input Number or backspace, so when the length of string is 0, it must be backspace key.
Hope the above will do some help.
Rather than trying to preconstruct what WILL BE in the text field or figure out what special character has been entered in the shouldChangeCharactersInRange method, I would suggest doing the following:
[self performSelector:#selector(manageSearchResultsDisplay)
withObject:nil
afterDelay:0];
This allows you to call a method directly after the current operation completes. What's cool about this is that, by the time it completes, the modified value will already be in the UITextField. At that point, you can just check its length and/or validate based on what's there.
Subclassing UITextField did not work for me on iOS 8.3, deleteBackward was never called.
Here is the solution I used, works on all iOS 8 versions and should work on other iOS versions as well
for textField in textFields {
textField.text = " "
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if string == "" && textField.text == " " {
// Do stuff here
return false
}
return true
}
I have implemented the similar solution with minor improvements that will tell me that if the text field has any value while the user has tapped the backspace. This is useful for my case when I should only focus on another text field if the text field is empty when backspace pressed.
protocol MyTextFieldDelegate : UITextFieldDelegate {
func textFieldDidDelete(textField: MyTextField, hasValue: Bool)
}
override func deleteBackward() {
let currentText = self.text ?? ""
super.deleteBackward()
let hasValue = currentText.isEmpty ? false : true
if let delegate = self.delegate as? MyTextFieldDelegate {
delegate.textFieldDidDelete(textField: self, hasValue: hasValue)
}
}
The most poplar answer is missing one thing — the ability to detect whether the text field was empty or not.
That is, when you override the deleteBackwards() method of a TextField subclass, you still don't know whether the text field was already empty. (Both before and after deleteBackwards(), textField.text! is an empty string: "")
Here's my improvement, with a check for emptiness prior to deletion.
1. Create a delegate protocol that extends UITextFieldDelegate
protocol MyTextFieldDelegate: UITextFieldDelegate {
func textField(_ textField: UITextField, didDeleteBackwardAnd wasEmpty: Bool)
}
2. Subclass UITextField
class MyTextField: UITextField {
override func deleteBackward() {
// see if text was empty
let wasEmpty = text == nil || text! == ""
// then perform normal behavior
super.deleteBackward()
// now, notify delegate (if existent)
(delegate as? MyTextFieldDelegate)?.textField(self, didDeleteBackwardAnd: wasEmpty)
}
}
3. Implement your new delegate protocol
extension MyViewController: MyTextFieldDelegate {
func textField(_ textField: UITextField, didDeleteBackwardAnd wasEmpty: Bool) {
if wasEmpty {
// do what you want here...
}
}
}
Comprehensive handler for textfield with single digit number for Swift 5.1:
Assuming that you have outlet collection of textFields (with connected delegates as well)
1 Step
protocol MyTextFieldDelegate: class {
func textField(_ textField: UITextField, didDeleteBackwardAnd wasEmpty: Bool)
}
final class MyTextField: UITextField {
weak var myDelegate: MyTextFieldDelegate?
override func deleteBackward() {
let wasEmpty = text == nil || text == ""
// then perform normal behavior
super.deleteBackward()
// now, notify delegate (if existent)
(delegate as? MyTextFieldDelegate)?.textField(self, didDeleteBackwardAnd: wasEmpty)
}
}
2 Step
final class ViewController: UIViewController {
#IBOutlet private var textFields: [MyTextField]!
override func viewDidLoad() {
super.viewDidLoad()
textFields.forEach {
$0.delegate = self
$0.myDelegate = self
}
}
}
3 Step
extension ViewController: UITextFieldDelegate, MyTextFieldDelegate {
func textFieldHasChanged(with text: String, _ tag: Int, for textField: UITextField) {
textField.text = text
if let someTextField = (textFields.filter { $0.tag == tag }).first {
someTextField.becomeFirstResponder()
} else {
view.endEditing(true)
}
}
func textField(_ textField: UITextField, didDeleteBackwardAnd wasEmpty: Bool) {
// If the user was pressing backward and the value was empty, go to previous textField
textFieldHasChanged(with: "", textField.tag - 1, for: textField)
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Restrict to only digits
let aSet = NSCharacterSet(charactersIn: "0123456789").inverted
let compSepByCharInSet = string.components(separatedBy: aSet)
let numberFiltered = compSepByCharInSet.joined(separator: "")
guard string == numberFiltered, let text = textField.text else { return false }
if text.count >= 1 && string.isEmpty {
// If the user is deleting the value
textFieldHasChanged(with: "", textField.tag - 1, for: textField)
} else {
textFieldHasChanged(with: string, textField.tag + 1, for: textField)
}
return false
}
}
Here my solution based on #andrew idea:
somewhere, for example in viewDidLoad
textField.delegate = self
textField.addTarget(self, action: #selector(valueChanged(_:)), for: .editingDidBegin)
and then
#objc func valueChanged(_ textField: UITextField) {
textField.text = "\u{200B}"
}
override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
textField.text = string
if string == "" {
//backpaspace pressed
}
All the answers are very helpful and I don't know why everyone is taking the protocol route. You can do it with much less code with call back function like-
Swift 5.0 or above
Make a custom textfield class extending the UITextField and override the deleteBackward function-
class CustomTextField: UITextField {
var backButtonPressedInEmptyTextField: (()->())?
override func deleteBackward() {
super.deleteBackward()
if let text = self.text, text.count == 0{
backButtonPressedInEmptyTextField?()
print("Back space clicked when textfield is empty")
}
}
}
Let's assume you want to do something based on that in your ViewController, MyViewController. So, in the MyViewController, just do following-
class MyViewController: UIViewController{
#IBOutlet weak var sampleTextField: CustomTextField!{
didSet{
sampleTextField.backButtonPressedInEmptyTextField =
backButtonPressed()
}
}
func backButtonPressed(){
//do whatever you want
}
}
I feel with closure or call-back function, it is much cleaner.
Something like this:
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
if (![text hash] && ![textField.text length])
[self backspaceInEmptyTextField];
}
of course the hash is for one character string.
Using the TextField Delegate method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Add the following code in above method to detect delete event
if(textField == YourTextField)
{
if ([string length] == 0 && range.length > 0)
{
// Your Code after deletion of character
}
}
+ (BOOL)detectBackspaceOnly:(NSString *)string
{
for(int i=0 ; i<string.length ; i++){
unichar caract = [string characterAtIndex:i];
if(caract != ' ' && caract != '\n')
return NO;
}
return YES;
}
You can check the text of the text view/field to see if it's empty and make sure the replacement text is also empty in the shouldChangeTextIn delegate method.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (textView.text == "" && text == "") {
print("Backspace on empty text field.")
}
return true
}
To Keep it Simple here is the only condition u need to check
if (range.length==1)
In UITextViewDelegate:
- (BOOL) textView:(UITextView *)textView
shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
if(text isEqualToString:#"");
{
NSLog(#"press backspace.");
}
}
it works ok for me.
update for Chinese simplified pinyin and Chinese handwriting input:
- (BOOL) textView:(UITextView *)textView
shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
if (range.length > 0 && [text isEqualToString:#""]) {
NSLog(#"press Backspace.");
}
return YES;
}
base on the document says:
"If the user presses the deleteKey, the length of the range is 1 and an empty string object replaces that single character."
Related
I have a custom UITableViewCell in my UITableView that contains multiple UITextFields. My ViewController that contains my tableView implements UITextFieldDelegate. This means that the UITextField delegate methods are triggered when the user is interacting with the textFields.
My problem is that I need to access the textFields that are contained inside the custom UITableViewCell from inside the UITextField delegate methods. The challenge is this: unfortunately, I am unable to use the "tag" property that the UITextField has. The "tag" property is being used for something else, and at this point in the project I unfortunately can't refactor it.
For example, in the following delegate method:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//instead of: if textField.tag == someNumber
//I would like to have something like:
//if textField == cell.textFieldA {
//...
//} else if textField == cell.textFieldB {
//...
//}
...
}
Does anyone have any ideas or suggestions?
you can create an extension of UITextField. something like this:
extension UITextField {
private struct AssociatedKeys {
static var someId = "someId"
}
#IBInspectable var someId: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.someId) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(self, &AssociatedKeys.someId, newValue as NSString?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
Then you can access and compare:
textField.someId
You can try below code. i have tried it in Objective C
NSIndexPath *indexPath = [self.tableview indexPathForRowAtPoint:textField.superview.superview.center];
CustomTableViewCell *cell = [self.tableview cellForRowAtIndexPath:indexPath];
//cell.txt1 is your textfield Object
if(textField == cell.txt1)
{
}
You can convert it in swift.
In the following code I am trying to transfer control from UITextField to the next via a next button.
I am doing this by calling becomeFirstResponder on the next UITextField.
If I don't type anything in the first and current UITextField the next button works as expected. The keyboard stays up and the focus is transferred.
If I do type something, and only if the field is empty. The method becomeFirstResponder for the next field is called and returns true, yet the keyboard is dismissed and focus is not transferred.
public func numberPad(numberPad: APNumberPad, functionButtonAction:UIButton, textInput: UIResponder) {
var current:UITextField?
for field in editCells {
if (current != nil) {
field.valueTextField.becomeFirstResponder()
return;
}
if (field.valueTextField == activeField) {
current = field.valueTextField
}
}
textInput.resignFirstResponder()
}
This function is called when the NEXT or DONE button is pressed on the keyboard. Which is a custom number keypad. APNumberPad specifically.
https://github.com/podkovyrin/APNumberPad
It is my delegate function.
Anyone know any reason becomeFirstResponder would return true and not work, only in some cases, but work in others?
And yes this is the main UI thread. Adding a call to resignFirstResponder on the current field, then a delay and calling becomeFirstResponder works. This causes the keypad to flicker, no matter how small the delay though.
Edit... I am now doing this... and am living with the keyboard flicker for now:
Delay is a helper function for GCD
public func numberPad(numberPad: APNumberPad, functionButtonAction:UIButton, textInput: UIResponder) {
var current:UITextField?
for field in editCells {
if (current != nil) {
current?.resignFirstResponder()
delay (0) {
field.valueTextField.becomeFirstResponder()
}
return;
}
if (field.valueTextField == activeField) {
current = field.valueTextField
}
}
textInput.resignFirstResponder()
}
I don't know if it helps you, or not. I wrote a simple UITextField extension that contains a returnView variable which decides what the textfield should do on return key press:
turn to next text field (if the returnView is an UITextField)
simulate button touch (if the returnView is a UIButton)
or hide keyboard
class NextTextField: UITextField, UITextFieldDelegate {
#IBOutlet weak var returnView: UIView? {
didSet {
if returnView is UITextField {
returnKeyType = UIReturnKeyType.Next
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
if let nextTextField = self.returnView as? UITextField {
nextTextField.becomeFirstResponder()
} else if let nextButton = self.returnView as? UIButton {
nextButton.sendActionsForControlEvents(.TouchUpInside)
} else {
self.resignFirstResponder()
}
return true
}
}
I would like to get rid of the "return" function of the keyboard while the user is typing, so there are no new lines, so instead I would like the 'return' key to function as 'Done' so it would hide the keyboard.
I am using a UITextView, that is editable, so the user is able to type their post, and post it to the main timeline, but since I have fixed cells, I don't want the user to be able to press 'return' and their post would be out of range of the timeline.
I found this that works with UITextField, but not with UITextView:
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder() //if desired
return true
}
So I just wanted to know if there is a way to do that in a UITextView, or at least to be able to hide the keyboard if pressed return, instead of creating a new line.
You can set the return key type of the text field:
textField.returnKeyType = UIReturnKeyType.done
Update
You can definitely use the same approach to set the return key to "Done", as mentioned above. However, UITextView doesn't provide a callback when user hits the return key. As a workaround, you can try to handle the textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) delegate call, and dismiss the keyboard when you detect the input of a new line character:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
}
return true
}
I have tried many codes and finally this worked for me in Swift 3.0 Latest [April 2019] this achieved using UITextFields
The "ViewController" class should be inherited the "UITextFieldDelegate" for making this code working.
class ViewController: UIViewController,UITextFieldDelegate
Add the Text field with the Proper Tag number and this tag number is used to take the control to appropriate text field based on incremental tag number assigned to it.
override func viewDidLoad() {
userNameTextField.delegate = self
userNameTextField.tag = 0
userNameTextField.returnKeyType = .next
passwordTextField.delegate = self
passwordTextField.tag = 1
passwordTextField.returnKeyType = .go
}
In the above code, the "returnKeyType = UIReturnKeyType.next" where will make the Key pad return key to display as "Next" you also have other options as "Join/Go" etc, based on your application change the values.
This "textFieldShouldReturn" is a method of UITextFieldDelegate controlled and here we have next field selection based on the Tag value incrementation.
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
return true;
}
return false
}
If you're working with a storyboard or xib, you can change the UITextView's Return button to 'Done' (or various other options) within Interface Builder, without the need for any setup code. Just look for this option in the Attributes inspector:
From there, you just pair it up with the UITextViewDelegate code that others have already provided here.
Swift v5:
extension ExampleViewController: UITextViewDelegate {
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
}
return true
}
}
And then, in your viewDidLoad() method:
exampleTextView.delegate = self
Working in Swift 4
Add this in viewDidLoad().
textField.returnKeyType = UIReturnKeyType.Done
Add this anywhere you like.
extension UITextView: UITextViewDelegate {
public func textViewDidChange(_ textView: UITextView) {
if text.last == "\n" { //Check if last char is newline
text.removeLast() //Remove newline
textView.resignFirstResponder() //Dismiss keyboard
}
}
}
I have a question about iOS UIKeyboard.
I have a UITextField and I would to have the keyboard with only uppercase characters.
I use a storyboard and I tried to set the Cpitalization as "All characters" to UITextField properties.
But this not solve my problem...any suggestion?
Set your textfield type autocapitalizationType to UITextAutocapitalizationTypeAllCharacters on the UITextField
self.yourTexField.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
After call delegate
// delegate method
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSRange lowercaseCharRange = [string rangeOfCharacterFromSet:[NSCharacterSet lowercaseLetterCharacterSet]];
if (lowercaseCharRange.location != NSNotFound) {
textField.text = [textField.text stringByReplacingCharactersInRange:range
withString:[string uppercaseString]];
return NO;
}
return YES;
}
Swift 5.4.2
self.yourTextField.autocapitalizationType = .allCharacters
One issue I have with some of the above answers is if you try and set textfield.text, you will lose the cursor position. So if a user tries to edit the middle of the text, the cursor will jump to the end.
Here is my Swift solution, still using UITextFieldDelegate:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == textFieldToUppercase {
if string == "" {
// User presses backspace
textField.deleteBackward()
} else {
// User presses a key or pastes
textField.insertText(string.uppercaseString)
}
// Do not let specified text range to be changed
return false
}
return true
}
For those looking for a Swift version.
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
textField.text = (textField.text! as NSString).replacingCharacters(in: range, with: string.uppercased())
return false
}
Original answer
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
textField.text = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string.uppercaseString)
return false
}
Using the Capitalization: All Characters property just forces keyboard to open with caps lock on, but lets the user to turned it off.
The syntax is now
Swift 2
textField.autocapitalizationType = UITextAutocapitalizationType.AllCharacters
Swift 3
textField.autocapitalizationType = .allCharacters
This is a different approach I used, where it does the following:
Enforces capitalization as soon as the character is entered
Catches situations where the user disables caps lock even if it textfield is set to auto caps
Allows for easy editing
Works with Swift 2.2
First, register a notification to be updated whenever any changes occur in the textfield.
textField.addTarget(self, action: #selector(YourClassName.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingChanged)
Then, implement textFieldDidChange.
func textFieldDidChange(textField: UITextField) {
textField.text = textField.text?.uppercaseString
}
I chose this to avoid a situation where the user sees an uneven experience of some capitalized, but then changed once they move to the next character.
You should avoid to use delegate method
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
because this will trigger an unwanted behaviour with iOS 13 + QuickPath typing (the iOS' Swiftkey Keyboard counterpart).
If you swipe on the keyboard and write "hello", it will write "HELLOHELLOHELLOHELLOHELLO" into the textfield. This is because the method is called multiple times and it appends the just changed text via textField.text = uppercasedValue.
The right way is to observe the .editingChange event and uppercase then the value. For example:
func awakeFromNib() {
textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
and
#objc func textFieldDidChange() {
textField.text = textField.text?.uppercased()
}
Swift 3 / Swift 4 / Swift 5
Just one line code in ViewDidLoad/ViewDidAppear:
If you simply want to see the characters typed regardless of the UPPER/lower case to all CAPITALS/UPPER CASE paste below code either in ViewDidLoad/ViewDidAppear
self.myTextField.autocapitalizationType = .allCharacters
above line changes all letters into CAPITALS while you type automatically
Set UITextField property autocapitalizationType to UITextAutocapitalizationTypeAllCharacters. This will make all characters to appear in upper case. Also visit here to find more about textfields
SwiftUI
For SwiftUI the Syntax for autocapitalization and Keyboard type selection is:
TextField("Your Placeholder", text: $emailAddress)
.keyboardType(.emailAddress)
.autocapitalization(.none)
You can use the following options for autocapitalization:
.none //Specifies that there is no automatic text capitalization.
.words //Specifies automatic capitalization of the first letter of each word.
.sentences //Specifies automatic capitalization of the first letter of each sentence.
.allCharacters //Specifies automatic capitalization of all characters, such as for entry of two-character state abbreviations for the United States.
Swift 4.0 Version:
First set the delegate for the textfield you want to uppercase to the current ViewController (click drag from the textfield to the currentViewController to set the delegate).
After add the extension:
extension CurrentViewController: UITextFieldDelegate{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//refference to the textfield you want to target
if textField.tag == 5{
textField.text = (textField.text! as NSString).replacingCharacters(in: range, with: string.uppercased())
return false
}
return true
}
}
You can also use this code.
-(BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
// Uppercase for string which you need
textField.text = [textField.text stringByReplacingCharactersInRange:range
withString:[string uppercaseString]];
// return NO because You have already done it in above code
return NO;
}
The simplest way would be to implement the editing changed method of the text field and set the textfield's text value to upper case representation of the entered text.
#property (nonatomic, strong) IBOutlet UITextField *yourTextfield
// add target in code or use interface builder
[self.yourTextField addTarget:self
action:#selector(uppercaseTextField)
forControlEvents:UIControlEventEditingChanged];
- (IBAction)uppercaseTextField:(UITextField*)textField
{
textField.text = [textField.text uppercaseString];
}
Finally I found the way that respects also editing text in the middle of the string in UITextField.
The problem is that if you replace whole text by UITextFiled.text property the actual cursor moves to end of text. So you need to use .replace() method to specify exactly which characters you want to update to upperCase.
Last thing is to return string.isEmpty as return value of function - otherwise you are not allowing deleting of text.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text, let textRange = Range(range, in: text) {
let uppercasedString = string.uppercased()
let updatedText = text.replacingCharacters(in: textRange, with: uppercasedString)
if let selectedTextRange = textField.selectedTextRange {
textField.replace(selectedTextRange, withText: uppercasedString)
approveButtonState(vin: updatedText)
}
return string.isEmpty
}
return false
}
Maybe it's a bit late for an answer here, but as I have a working solution someone might find it useful.
Well, in the following textfield delegate method, check if the new string contains any lowercase characters. If so, then:
Append the character that was just typed to the textfield's text.
Make all the textfield's text uppercased.
Make sure that false is returned by the method.
Otherwise just return true and let the method work as expected.
Here's its implementation:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var returnValue = true
let lowercaseRange = string.rangeOfCharacter(from: CharacterSet.lowercaseLetters)
if let _ = lowercaseRange?.isEmpty {
returnValue = false
}
if !returnValue {
textField.text = (textField.text! + string).uppercased()
}
return returnValue
}
The above has worked perfectly for me, and a similar implementation works for textviews too, after making the proper adjustments first of course.
Hope it helps!
/**
We take full control of the text entered so that lowercase cannot be inserted
we replace lowercase to uppercase
*/
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// No spaces allowed
if string == " " {
return false
}
// delete key pressed
if string == "" {
textField.deleteBackward()
return false
}
// We only allow alphabet and numbers
let numbersAndLettersSet = CharacterSet.alphanumerics
if string.lowercased().rangeOfCharacter(from: numbersAndLettersSet) == nil {
return false
}
// Add the entered text
textField.insertText(string.uppercased())
// Return false as we are doing full control
return false
}
Here there's my situation and how I achieved to force the upper text:
custom class (UITextField subclass)
don't want to use delegate UITextFieldDelegate methods
Solution proposed from #CodeBender was pretty much what I was looking for but the cursor always jump to the end as noticed from #Dan.
class MyCustomTextField: UITextField {
...
addTarget(self, action: #selector(upperText), for: .editingChanged)
...
...
#objc private func upperText() {
let textRange = selectedTextRange
text = text?.uppercased()
selectedTextRange = textRange
}
This will set the cursor always in the correct position (where it was) even if user adds text in "the middle".
On text change we can change to uppercase
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == txtEmail {
textField.text = (textField.text! as NSString).replacingCharacters(in: range, with: string.uppercased())
return false
}
return true
}
Using the following text field delegate method it can be done:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
//--- Making uppercase ---//
if (textField == yourTextField ) {
NSRange lowercaseCharRange;
lowercaseCharRange = [string rangeOfCharacterFromSet:[NSCharacterSet lowercaseLetterCharacterSet]];
if (lowercaseCharRange.location != NSNotFound) {
textField.text = [textField.text stringByReplacingCharactersInRange:range
withString:[string uppercaseString]];
return NO;
}
}
}
Hope this helps.
I'm making a UITextField that has a UIPickerView as inputView. Its all good, except that I can edit by copy, paste, cut and select text, and I don't want it. Only the Picker should modify text field.
I've learned that I can disable editing by setting setEnabled or setUserInteractionEnabled to NO. Ok, but the TextField stop responding to touching and the picker don't show up.
What can I do to achieve it?
Using the textfield delegate, there's a method
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Return NO from this, and any attempt by the user to edit the text will be rejected.
That way you can leave the field enabled but still prevent people pasting text into it.
Translate the answer of Nick to swift:
P/S: Return false => the textfields cannot input, edit by the keyboard. It just can set text by code.EX: textField.text = "My String Here"
override func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return false
}
This would be the simplest of all:
in viewDidLoad:(set the delegate only for textfields which should not be editable.
self.textfield.delegate=self;
and insert this delegate function:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
return NO;
}
Thats it!
In swift 3+ :
class MyViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
self.myTextField.delegate = self
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if textField == myTextField {
// code which you want to execute when the user touch myTextField
}
return false
}
}
Simply place a UIButton exactly over the entire UITextField with no Label-text which makes it "invisible". This button can receive and delegate touches instead of the Textfield and the content of the TextField is still visible.
It would be more elegant to create a custom subclass of UITextField that returns NO for all calls to canPerformAction:withSender: (or at least where action is #selector(cut) or #selector(paste)), as described here.
In addition, I'd also implement
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
as per Nick's suggestion in order to disable inputting text from Bluetooth keyboards.
In Swift:
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
questionField.resignFirstResponder();
// Additional code here
return false
}
I used the solution provided by MrMage. The only thing I'd add is you should resign the UITextView as first responder, otherwise you're stuck with the text selected.
Here's my swift code:
class TouchableTextView : UITextView {
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
self.resignFirstResponder()
return false
}
override func shouldChangeTextInRange(range: UITextRange, replacementText text: String) -> Bool {
self.resignFirstResponder()
return false
}
}
To prevent editing of UITextField while using UIPickerView for selecting values(in Swift):
self.txtTransDate = self.makeTextField(self.transDate, placeHolder: "Specify Date")
self.txtTransDate?.addTarget(self, action: "txtTransDateEditing:", forControlEvents: UIControlEvents.EditingDidBegin)
func makeTextField(text: String?, placeHolder: String) -> UITextField {
var textField = UITextField(frame: CGRect(x: 140, y: 0, width: 220.00, height: 40.00));
textField.placeholder = placeHolder
textField.text = text
textField.borderStyle = UITextBorderStyle.Line
textField.secureTextEntry = false;
textField.delegate = self
return textField
}
func txtTransDateEditing(sender: UITextField) {
var datePickerView:UIDatePicker = UIDatePicker()
datePickerView.datePickerMode = UIDatePickerMode.Date
sender.inputView = datePickerView
datePickerView.addTarget(self, action: Selector("datePickerValueChanged:"), forControlEvents: UIControlEvents.ValueChanged)
}
func datePickerValueChanged(sender: UIDatePicker) {
var dateformatter = NSDateFormatter()
dateformatter.dateStyle = NSDateFormatterStyle.MediumStyle
self.txtTransDate!.text = dateformatter.stringFromDate(sender.date)
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
self.resignFirstResponder()
return false
}
For an alternative that handles the UIPickerView and Action Sheets, checkout ActionSheetPicker
https://github.com/TimCinel/ActionSheetPicker
It's cocoapods enabled. It handles all of the cancel and done buttons on the Action Sheet. The examples within the sample project are great. I choose the ActionSheetStringPicker, which handles easily just String based options, but the API can handle most anything that I can think of.
I originally started a solution much like the checkmarked answer, but stumbled onto this project and took me roughly 20 minutes to get things integrated into my app for usage including using cocopods: ActionSheetPicker (~> 0.0)
Hope this helps.
Download the git project and look at the following classes:
ActionSheetPickerViewController.m
ActionSheetPickerCustomPickerDelegate.h
Here is roughly most of the code that I added, plus the *.h imports.
- (IBAction)gymTouched:(id)sender {
NSLog(#"gym touched");
[ActionSheetStringPicker showPickerWithTitle:#"Select a Gym" rows:self.locations initialSelection:self.selectedIndex target:self successAction:#selector(gymWasSelected:element:) cancelAction:#selector(actionPickerCancelled:) origin:sender];
}
- (void)actionPickerCancelled:(id)sender {
NSLog(#"Delegate has been informed that ActionSheetPicker was cancelled");
}
- (void)gymWasSelected:(NSNumber *)selectedIndex element:(id)element {
self.selectedIndex = [selectedIndex intValue];
//may have originated from textField or barButtonItem, use an IBOutlet instead of element
self.txtGym.text = [self.locations objectAtIndex:self.selectedIndex];
}
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
return NO; // Hide both keyboard and blinking cursor.
}
if you are ready to create your custom textfield then you can just use this answer from another stackoverflow question.
https://stackoverflow.com/a/42698689/9369035
just override those three method as in above answer and that is enough. at least so far as I tested.
This workaround works. Put a transparent UIView above the text field and implement the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *press = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress)];
[transparentView addGestureRecognizer:press];
[press release];
press = nil;
}
-(void)longPress
{
txtField.userInteractionEnabled = NO;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
txtField.userInteractionEnabled = YES;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[txtField becomeFirstResponder];
}
Make your inputView be presented by an hidden textfield which also change the text of the presented and disabled one.
Not completely sure about how hacky this is but the only thing that did the trick in my case was adding a target action and calling endEditing. Since my picker controls my UITextField.text value, I could dismiss the keyboard as soon as the user clicks on the field. Here's some code:
uiTextFieldVariable.addTarget(self, action: #selector(dismissKeyboard), for: .editingDidBegin)
#objc private func dismissKeyboard() {
endEditing(true)
}
If you are using IQKeyboardManagerSwift pod then use textField.enableMode = .disabled
else If you are using RxSwift & RxCocoa pods then
textField.rx.controlEvent(.editingDidBegin)
.subscribe(onNext: {
[weak self] _ in let _ = self?.textField.endEditing(true)
}).disposed(by: bag)
else use delegate method of textFiled
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return false
}
I used :
[self.textField setEnabled:NO];
and its work fine
This worked for me
[textview setEditable:NO];
The above answers are overcomplicating the situation.