Hide the cursor of a UITextField
Asked Answered
C

15

164

I am using a UITextField with a UIPickerView for its inputView, so that when the user taps the text field, a picker is summoned for them to select an option from.

Nearly everything works, but I have one problem: the cursor still flashes in the text field when it is active, which is ugly and inappropriate, since the user is not expected to type into the field and is not presented with a keyboard. I know I could hackily solve this by setting editing to NO on the text field and tracking touches on it, or by replacing it with a custom-styled button, and summoning the picker via code. However, I want to use the UITextFieldDelegate methods for all the event handling on the text field and hacks such as replacing the text field with a button do not permit this approach.

How can I simply hide the cursor on the UITextField instead?

Cockroach answered 13/9, 2010 at 10:53 Comment(0)
B
288

Simply subclass UITextField and override caretRectForPosition

- (CGRect)caretRectForPosition:(UITextPosition *)position
{
    return CGRectZero;
}
Bk answered 1/12, 2012 at 14:44 Comment(8)
@Joseph Chiu It works great. But not with iOS 4.3. Could you help me with this?Totter
the cleanest and shortest approach which deserves a double upvote =)Adder
Just a note. In my subclass I added a bool hideCaret then in this override If it is true -> return CGRectZero else return the result of super.Dong
How do you trigger the system to ask you for a new caret position? i.e. how could I toggle the caret on and off without changing the contents of the text field?Dimmick
Just be aware that users with an external keyboard can change the text field's value even if the cursor is hidden and you use a picker view.Discourse
yep doesn't prevent input via hardware keyboard so not really a solutionVinita
@malhal, hiding the cursor was all the OP asked for.Sizeable
You can combine this solution with UITextFieldDelegate's textField(_:shouldChangeCharactersIn:replacementString:) and always return false.Sizeable
L
171

As of iOS 7 you can now just set the tintColor = [UIColor clearColor] on the textField and the caret will disappear.

Lahey answered 20/11, 2013 at 16:26 Comment(8)
This works at the moment, however I would advise against using it since it might change in the future. Rather opt for the caretRectForPosition: override solution.Crinum
@Crinum True, that is probably a better way.Lahey
Good enough for now. Sometimes you just need a quick solution.Commissioner
This is the easiest solution by far!Barra
This answer should be approved for iOS 7+Applewhite
Good solution and very easy to use, Swift 4 update: textField.tintColor = .clearTomasatomasina
@Crinum why would this behavior change in the future? The tintColor is an inherit property of the textField control and is meant to be used this way. By this logic, you should never change any properties of any ui object because they might change in the future. This answer is just as appropriate as the accepted answer.Equality
It works but the caret will show once a user make a selection. Overriding a caretRect is more universal solutionHerta
B
116

You can just clear the textfield's tintColor

self.textField.tintColor = [UIColor clearColor];

Swift 3.0

self.textField.tintColor = .clear

enter image description here

Bunce answered 1/5, 2015 at 14:50 Comment(3)
Best easiest answer.Unstop
As mentioned above, clearing the tint color doesn't stop users with external keyboards (iPad Pro) from changing the text.Survivor
@MichaelLong this isn't about stopping the user from changing the text, it's just a styling choice.Equality
S
21

You might also want to stop the user from selecting, copying or pasting any text so that the only text input comes from the picker view.

- (CGRect) caretRectForPosition:(UITextPosition*) position
{
    return CGRectZero;
}

- (NSArray *)selectionRectsForRange:(UITextRange *)range
{
    return nil;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    if (action == @selector(copy:) || action == @selector(selectAll:) || action == @selector(paste:))
    {
        returnNO;
    }

    return [super canPerformAction:action withSender:sender];
}

http://b2cloud.com.au/tutorial/disabling-the-caret-and-text-entry-in-uitextfields/

Settera answered 9/10, 2014 at 13:31 Comment(0)
D
15

Check out the property selectedTextRange of the protocol UITextInput, to which the class UITextField conforms. Few! That's a lesson in object-oriented programing right there.

Hide Caret

To hide the caret, nil out the text field's selected text range.

textField.selectedTextRange = nil; // hides caret

Unhide Caret

Here are two ways to unhide the caret.

  1. Set the text field's selected text range to the end of the document.

    UITextPosition *end = textField.endOfDocument;
    textField.selectedTextRange = [textField textRangeFromPosition:end
                                                        toPosition:end];
    
  2. To keep the caret in the same spot, first, store the text field's selected text range to an instance variable.

    _textFieldSelectedTextRange = textField.selectedTextRange;
    textField.selectedTextRange = nil; // hides caret
    

    Then, when you want to unhide the caret, simply set the text field's selected text range back to what it was originally:

    textField.selectedTextRange     = _textFieldSelectedTextRange;
    _textFieldLastSelectedTextRange = nil;
    
Demolish answered 1/3, 2013 at 13:44 Comment(8)
This particular solution didn't work for my implementation. The cursor still blinks.Whitver
Well, then, perhaps you should file a bug at bugreport.apple.com because the iOS docs say: "If the text range has a length, it indicates the currently selected text. If it has zero length, it indicates the caret (insertion point). If the text-range object is nil, it indicates that there is no current selection."Demolish
I don't care enough to file a report. If others use your "solution" and don't see it working I wanted them to know they're not alone.Whitver
Despite @ArtGeigel's comments, this works perfectly for me. However, I kind of prefer the solution involving overriding caretRectForPosition. It's more explicit what it's doing, and the docs that you've quoted don't make clear what the behaviour of the caret should be when there is 'no current selection'. If @ArtGeigel's claim that this doesn't work were correct (which it isn't, at least as far as I can see) it wouldn't be clear that that were a bug.Flessel
Didn't work for me either. Caret is still there and blinking.Damn
Worked great for me too! and simpler than subclassing, especially when you need to remove the caret on demandKirwin
There are a couple of gotchas which might affect your usage: 1) The user can still bring the cursor onscreen if they want by tapping on the field 2) Any time you programatically change the field's text, it'll want to turn the caret back on; you can avoid this by turning it off again straight afterHerzig
I can assure you guys this solution WORKS! Thanks.Bessette
L
13

Swift 5 version of Net's post

  override func caretRect(for position: UITextPosition) -> CGRect {
    return .zero
  }
  
  override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
    return []
  }
  
  override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    return false
  }
Latium answered 9/3, 2017 at 15:4 Comment(1)
In my case it worked. Added a subclass of UITextField with these methods using Swift 4. Thanks!Apocryphal
E
11

Answer provided by the OP, copied from the question body to help clean up the ever growing tail of unanswered questions.

I found another solution: subclass UIButton and override these methods

- (UIView *)inputView {
    return inputView_;
}

- (void)setInputView:(UIView *)anInputView {
    if (inputView_ != anInputView) {
        [inputView_ release];
        inputView_ = [anInputView retain];
    }
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

Now the button, as a UIResponder, have a similar behavior than UITextField and an implementation pretty straightforward.

Experimentalize answered 13/9, 2010 at 10:53 Comment(6)
This is not really a great solution. Check out this answer below: https://mcmap.net/q/149327/-hide-the-cursor-of-a-uitextfieldTyus
Why isn't this a great solution? It achieves the intended effect and also provides functionality to a class that previously didn't have it. It's also not a hack. I think it's really cool. So what's wrong with it?Semblance
@Semblance The biggest problem I can see is that you can't use a UITextFieldDelegate with this to handle begin and end editing events. Instead - unless there's a way to handle those events that I don't know about - you'll need to override becomeFirstResponder and resignFirstResponder in the button subclass, and possibly create your own delegate protocol, add in a delegate property, and call the delegate from the aforementioned methods. This is all far more work than just overriding caretRectForPosition in a UITextField subclass.Flessel
@Semblance That said, despite Joseph Chiu's answer being superior from any practical standpoint, I still agree with you that this is pretty sexy. I'd never looked carefully at the UIResponder class reference before, and had no idea that a trick like this was possible.Flessel
Late to the party, but I think this is a very good solution and it feels much more appropriate and less of a hack than using a UITextField and hiding the cursor, which is basically a hack since we are then using the text field as a label while not using any of the functionality of the UITextField.Gennagennaro
This also works in Swift, on iOS 11. I also had to override -touchesBegan:withEvent and call -becomeFirstResponder.Alexandros
A
4

set the tintColor to Clear Color

textfield.tintColor = [UIColor clearColor];

and you can also set from the interface builder

Alkyd answered 11/10, 2017 at 10:47 Comment(5)
You just copied an answer from over 2 years ago.Niddering
sorry my friend, but I didn't copy anything.Alkyd
That's interesting, "my friend". Your answer looks pretty close to @oldman's answer from May 1 '15. Can you tell me how yours is different?Niddering
As mentioned above, clearing the tint color doesn't stop users with external keyboards (iPad Pro) from changing the text.Survivor
The pro users can do what they please. They are pros: they know what they are doing ;^)Lederer
E
2

If you want to hide cursor, you can easily use this! It worked for me..

[[textField valueForKey:@"textInputTraits"] setValue:[UIColor clearColor] forKey:@"insertionPointColor"]
Ejector answered 24/9, 2013 at 7:37 Comment(1)
This is undocumented, as far as I know. It's possible that using this will get your app rejected for calling private APIs if you submit it to the app store, though I don't know of any submissions using this to test that speculation one way or another. It's a pity, because it'd be nice to be able to solve this problem without subclassing, and this answer enables that.Flessel
E
1

Answer provided by the OP, copied from the question body to help clean up the ever growing tail of unanswered questions.

I think I have the correct solution but If it can be improved will be welcome :) Well, I made a subclass of UITextField and overriden the method that returns the CGRect for the bounds

-(CGRect)textRectForBounds:(CGRect)bounds {
    return CGRectZero;
}

The problem? The text doesn't show because the rect is zero. But I added an UILabel as a subview of the control and overridden the setText method so, as we enter a text as usual, the text field text is nil and is the label which shows the text

- (void)setText:(NSString *)aText {
    [super setText:nil];

    if (aText == nil) {
        textLabel_.text = nil;
    }

    if (![aText isEqualToString:@""]) {
        textLabel_.text = aText;
    }
}

With this the thing works as expected. Have you know any way to improve it?

Experimentalize answered 13/9, 2010 at 10:53 Comment(1)
This answer is basically worthless now given that Joseph Chiu's alternative approach is very similar but much simpler. May I suggest just deleting it?Flessel
S
1

To both disable cursor and menu I use subclass with these 2 methods:

- (CGRect)caretRectForPosition:(UITextPosition *)position {
    return CGRectZero;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    [UIMenuController sharedMenuController].menuVisible = NO;
    self.selectedTextRange = nil;

    return NO;
}
Settera answered 15/9, 2014 at 11:17 Comment(0)
M
0

I simply subclass UITextField, and override layoutSubviews as follows:

- (void)layoutSubviews
{
    [super layoutSubviews];
    for (UIView *v in self.subviews)
    {
        if ([[[v class] description] rangeOfString:@"UITextSelectionView"].location != NSNotFound)
        {
            v.hidden = YES;
        }
    }
}

It's a dirty hack, and may fail in the future (at which point the cursor will be visible again - your app won't crash), but it works.

Musculature answered 4/12, 2011 at 7:42 Comment(0)
P
0

If you like cleaner = less code, use the interface builder:

Clear color

(Attributes inspector, view section.)

Populate answered 1/3, 2021 at 21:31 Comment(0)
L
0

In my case, overriding the caret rect wasn't enough. On iOS 15, the caret didn't appear, effectively, but the selection handles did.

Solved it with: override var canBecomeFirstResponder: Bool { return false } on the UITextView subclass.

Lail answered 13/7, 2022 at 19:12 Comment(0)
D
-1

You can add a BOOL cursorless property to UITextField in a category via associated objects.

@interface UITextField (Cursorless)

@property (nonatomic, assign) BOOL cursorless;

@end

Then use method swizzling to swizzle caretRectForPosition: with a method that toggles between CGRectZero and its default value using cursorless.

This leads to a simple interface via a drop-in category. This is demonstrated in the following files.

Simply drop them in and get the benefit of this simple interface

UITextField category: https://github.com/rexmas/RexDK/blob/master/RexDK/UI/UITextField%2BRXCursorless.h https://github.com/rexmas/RexDK/blob/master/RexDK/UI/UITextField%2BRXCursorless.m

Method Swizzling: https://github.com/rexmas/RexDK/blob/master/RexDK/Foundation/NSObject%2BRXRuntimeAdditions.h https://github.com/rexmas/RexDK/blob/master/RexDK/Foundation/NSObject%2BRXRuntimeAdditions.m

Delila answered 19/5, 2014 at 2:20 Comment(8)
a bad solution on SO many levels! for starters: Never override methods in categories. Avoid Method Swizziling unless you really, really need it (other have come up with good solutions to this question). It's makes your code so much more complicated.Turtleback
If you actually looked at the code you'd realize that no method is overridden in the category and method swizzling is implemented correctly. I've been using this implementation for years without fail.Delila
Also, explain what's complicated about adding an isolated code base of ~150 lines to permanently solve a continuously recurring problem? Not only will this NOT complicate your codebase but it provides the simplest possible interface; a single boolean to dictate the functionality.Delila
take a look at this excellent discussion about method swizzling #5339776 Especially on debugging. Method swizzling is an amazing feature, but IMHO should only be used when needed. This problem is easy to solve using one of the suggestions provided here, and will provide code easier to maintain and read for other programmers.Turtleback
I've read that before and I followed all the outlined guidelines in my implementation for correctness. Arguably, this is a solid example of when method swizzling wins out. It's an isolated, yet, common case of where the basic libraries fail to implement a widely needed feature across platforms in a simple manner. The other examples suggested do not semantically define a cursorless textfield, they only perform it through obfuscation of either the frame of the cursor or it's color. To a new programmer, this interface is the easiest to read and does exactly what is expected of it.Delila
To follow up on another one of your points, the code base is arguably very small and thus easy to debug. And you can simply ignore the implementation and everything still works as-is.Delila
Take a look at Joseph Chiu's answer. He is providing the same functionality, just as a subclass. It is then clear and concise for other programmers what's going on, when the read your code.Turtleback
Yes, and this option doesn't require a whole new subclass, on top of that it's not leaky so you can subclass to your hearts content and still get the value of this interface. Composition vs Inheritance, I'm sure you're already aware of the benefits.Delila

© 2022 - 2024 — McMap. All rights reserved.