How to disable UITextField editing but still accept touch? - ios

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.

Related

Is there a way to hide a UILabel when the user starts typing, and then make it re-appear if the user removes all input?

I'm trying to implement a search field where you type in some characters to show "possible searches". In the beginning there is a small text underneath the textbox that says "make sure to capitalize letters". This is just a UILabel. I want to make this label "disappear" when the user STARTS to type. But if the user backspaces enough to remove all text - I want the label to re-appear again.
The textbox is just made from UITextField.
This is all stored in a view.
Does anyone have an idea of how to implement something like this?
You Can do it using add a target to your textFiled with a selector like this
add this target to ur viewDidLoad method
yourTextFiled.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: UIControl.Event.editingChanged)
create selector method like this below your viewDidLoad Method and set if textfield.text.isEmpty than label is hidden like this
#objc func textFieldDidChange(_ textField: UITextField) {
if textField.text!.isEmpty {
label.isHidden = false
} else {
label.isHidden = true
}
}
Hope you get your desire thing !
You can do this by the textfield delegate method shouldChangeInCharacter and check when textfield is empty set the label isHidden property to false otherwise too true.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text {
if text.isEmpty {
label.isHidden = false
}
else {
label.isHidden = true
}
}
return true
}
Hope this will help you. Happy coding!

How to disable Long press gesture recogniser in UITextField and UISearchBar?

I come across many post about disabling long press in UItextView, but that same process is not working for UITextField and UISearchBar.
Is there any way to disable Long press on UITextField and UISearchBar so I can avoid Magnifying glass on long press?
I have already checked "Disable Magnifying Glass in UITextField" but solution given there is not actually disabling magnifying glass. Its just not allowing cursory to Move between text. but still displaying Magnifying glass.
I want to disable Long Press - to avoid Problem which I am facing with magnifying glass. I want to disable it now. will enable it again when I will able to fix this.
You can try following for disabling only built in long press gesture
for (UIGestureRecognizer *recognizer in textView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
recognizer.enabled = NO;
}
}
or
delegate method for all :-
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
return NO;
}
This combination in a custom class is the only thing I've been able to get to work for me, and on the first long press it still sometimes makes the magnifying glass appear:
//swift 4.2
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer)
{
if gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)
{
print(gestureRecognizer.name.unwrappedDebugString)
gestureRecognizer.isEnabled = false
}
return super.addGestureRecognizer(gestureRecognizer)
}
override func becomeFirstResponder() -> Bool
{
for recognizer in self.gestureRecognizers ?? []
{
if (recognizer is UILongPressGestureRecognizer)
{
recognizer.isEnabled = false
}
}
return super.becomeFirstResponder()
}
#Neeraj answer in Swift 4 :
class CustomTextView: UITextView {
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) {
gestureRecognizer.isEnabled = false
}
return super.addGestureRecognizer(gestureRecognizer)
}
}
One good and clean way to do this is you can create a custom class for the UITextField and in the custom class you can override the long-press gesture which is causing the magnification view appear.
This custom class can be used in all places where we don't want to show the magnification view for test field. Just use below class in place of UITextField class
class CustomTextView: UITextView {
override func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer) {
if (gestureRecognizer.isKindOfClass(UILongPressGestureRecognizer)) {
gestureRecognizer.enabled = false;
}
super.addGestureRecognizer(gestureRecognizer)
return
}
}

UITextField - delegate for clearButtonMode?

I want the X button to show only when
textField.clearButtonMode = .UnlessEditing
When the user pushes the X button, the textView becomes the first responder, and the keyboard pops up.
What do I do so that when the X button is pushed, the text only clears, but the textView does not become focused?
- (BOOL)textFieldShouldClear:(UITextField *)textField
this method is your want,you can check something in it.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextFieldDelegate_Protocol/index.html#//apple_ref/doc/uid/TP40006991-CH3-SW10
i hope above link can help you.
If you want it to be focus and able to key in, try
textField.becomeFirstResponder()
If you mean you want it not to show the keyboard and just clear the text, you can delegate your UITextField and use the function to return false:
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
textField.text = ""
return false
}
You can implement this delegate like the following:
func textFieldShouldClear(_ textField: UITextField) -> Bool {
textField.text = ""
return false
}

Swift - change action event to UITextField

i'm trying to change the default action when user press on a text field. I tried to connect my action at Editing Did Begin event using storyboard as you can see below. The problem is that the keyboard always appear but I want that the keyboard doesn't never appear.
I cannot delete text field delegate methods cause i have others text field in the same view.
How can i do?
Set your view controller as your textField delegate and implement UITextField textFieldShouldBeginEditing delegate method.
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if textField == yourTextField {
// implement custom behaviour
return false
}
return true
}
Update
Try this for single textfield keyboard never show
func textFieldDidBeginEditing(textField: UITextField) {
if textField == yourTextField
{
textField.inputView = UIView()
}
}
Simple:
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if(textField == YourtextField){
return false
}
return true
}
OR:
in your viewDidLoad() set yourTextField.inputView = UIView()
Works in both ways. Use suitable one.

Detect backspace in empty UITextField

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."

Resources