116

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?

Luiz de Prá
  • 1,475
  • 2
  • 12
  • 23

17 Answers17

141

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.

Nick Lockwood
  • 40,865
  • 11
  • 112
  • 103
  • 4
    If you still want to respond to touch, respond to "touch down" and not "touch up inside". Otherwise your event never gets called. – radven May 29 '12 at 18:52
  • @NickLockwood are there any ways that we can still allow the textField to become the first responder, but disable user interaction and hide the caret ? – onmyway133 Feb 26 '14 at 04:03
  • 1
    @entropy I'm assuming you want to be able to select the content so that the user can copy it? If you subclass UITextField you can override pretty much any behaviour - e.g. you could force the field to always select all whenever the user touches is, which would effectively hide the caret. – Nick Lockwood Feb 27 '14 at 11:36
  • What if I would like to do the same but in Swift? – SagittariusA Sep 16 '15 at 15:38
  • 1
    @LoryLory it's the same, just use Swift syntax for the delegate method instead. – Nick Lockwood Sep 16 '15 at 19:24
  • 7
    More precisely we can use "textFieldShouldBeginEditing" if required – Naveen Shan Sep 28 '15 at 09:15
  • @NaveenShan `texdtFieldShouldBeginEditing` returning `NO` prevent the picker view to appear, this is not a solution to op's problem. – itMaxence Nov 23 '21 at 13:40
27

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
}
lee
  • 7,955
  • 8
  • 44
  • 60
  • 4
    This doesn't work for me. Just long click the text field until the zoom appears and the iOS cut/copy/paste menu appears and I can use that to alter the text. – RowanPD Jul 20 '16 at 13:53
  • 4
    swift 4: ` func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { return false } ` – lionello Jan 23 '18 at 05:08
17

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!

Ankish Jain
  • 11,305
  • 5
  • 36
  • 34
9

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
   }
}
Nadeesha Lakmal
  • 433
  • 5
  • 9
8

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.

Timm Kent
  • 1,123
  • 11
  • 25
7

In Swift:

func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
    questionField.resignFirstResponder();
    // Additional code here
    return false
}
Saranya
  • 163
  • 2
  • 10
7

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.

Community
  • 1
  • 1
MrMage
  • 7,282
  • 2
  • 41
  • 71
  • 1
    Can't for the life of me work out how to do that. The subclass's `canPerformAction` and `shouldChangeCharactersInRange` methods are never called. – Nestor Mar 28 '14 at 11:36
3

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
    }

}
Leedrick
  • 41
  • 5
2

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
}
Thiru
  • 1,380
  • 13
  • 21
1

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.
}
Nick N
  • 984
  • 9
  • 22
1

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.

Njuacha Hubert
  • 388
  • 3
  • 14
0

Make your inputView be presented by an hidden textfield which also change the text of the presented and disabled one.

Sandro Vezzali
  • 147
  • 2
  • 4
0

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)
}
Bruno Myrrha
  • 51
  • 1
  • 5
0

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
 }
0

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];
}
cocoakomali
  • 1,346
  • 1
  • 8
  • 7
  • 1
    I'm confused of why you don't use a simple transparent UIButton + touchUpInside, and opted for UIView + gesture instead. – Chen Li Yong Oct 09 '17 at 01:43
-5

I used :

[self.textField setEnabled:NO];

and its work fine

Amar
  • 13,202
  • 7
  • 53
  • 71
Chris
  • 1
  • 1
-7

This worked for me [textview setEditable:NO]; The above answers are overcomplicating the situation.

Tolgab
  • 1
  • 3
  • 1
    The OP wrote "I learned that I can disable editing by setting setEnabled or setUserInteractionEnabled:NO to NO. Ok, but the TextField stop responding to touching and the picker don't show up." Your answer will stop all events, which wasn't desired outcome. – maninvan Oct 01 '15 at 06:21
  • @Tolgab not the even related solution for this OP! – Anurag Sharma May 22 '17 at 12:41