textview - put cursor at end of text when start editing - ios

I am editing text in a UITextView. When the field opens for editing I need the cursor to be positioned after the final character of the existing text. The behavior I see is that the cursor is positioned more or less under the point where I touch the UITextView to start editing -- perhaps at the end of the word I touch on. I have tried setting the textview.selectedRange in both textViewDidBeginEditing: and textViewShouldBeginEditing: but that had no effect at all. I tried selecting ranges of the existing text, like {1,2} and that didn't do anything either. It seems like the selectedRange is more-or-less a read-only value?
- (void) textViewDidBeginEditing:(UITextView *)textView {
// Position the insertion cursor at the end of any existing text
NSRange insertionPoint = NSMakeRange([textView.text length], 0);
textView.selectedRange = insertionPoint;
}
How do I get the cursor to the end of the text?

The post referred to by the comment by artud2000 contains a working answer. To summarize here, add:
EDIT: The original answer was not sufficient. I've added toggling the editable property which seems to be sufficient. The problem is that the tap gesture only goes to the handler a single time (at most) and subsequent taps on the UITextField start editing directly. If it isn't editable then the UITextView's gesture recognizers are not active and the one I placed will work. This may well not be a good solution but it does seem to work.
- (void)viewDidLoad {
...
tapDescription = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(tapDescription:)];
[self.descriptionTextView addGestureRecognizer:tapDescription];
self.descriptionTextView.editable = NO; // if not set in storyboard
}
- (void) tapDescription:(UIGestureRecognizer *)gr {
self.descriptionTextView.editable = YES;
[self.descriptionTextView becomeFirstResponder];
}
- (void) textViewDidEndEditing:(UITextView *)textView {
//whatever else you need to do
textView.editable = NO;
}
The default position of the cursor seems to be after any existing text which solved my problem, but if you want to you can select the text in textViewDidBeginEditing: by setting the selectedRange -- for instance:
- (void) textViewDidBeginEditing:(UITextView *)textView {
// Example: to select the second and third characters when editing starts...
NSRange insertionPoint = NSMakeRange(1, 2);
textView.selectedRange = insertionPoint;
}

Thanks for the answer Charlie Price. I used it to solve my similar problem on a UITextView. Here's the answer in Swift in case anyone needs it:
...
let tapDescription = UITapGestureRecognizer(target: self, action: #selector(MyViewController.tapDescription(_:)))
self.descriptionTextView.addGestureRecognizer(tapDescription)
self.descriptionTextView.editable = false
...
func tapDescription(gr: UIGestureRecognizer) {
self.descriptionTextView.editable = true
self.descriptionTextView.becomeFirstResponder()
}
func textViewDidEndEditing(textView: UITextView) {
self.descriptionTextView.editable = false
}

Related

How to word wrap text in a UITextView?

I have a UITextView contained within a UIScrollView. The UITextView is loaded dynamically depending on the UISegmentedControl, but I don't know how to word wrap it so the word moves to the next line.
.m:
#interface Tester ()
{
UITextView *sponsor;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
sponsorInfo = [ [UITextView alloc] initWithFrame:self.sponsorsScrollView.frame];
[sponsor setEditable:NO];
}
- (IBAction)control:(UISegmentedControl *)sender
{
if (self.sponsorSegmentedControl.selectedSegmentIndex == 0)
{
[self changeGenericAbout:self.cellIndex];
}
else
{
//
}
}
- (void)changeGenericAbout:(int)index
{
if (index == 0)
{
sponsorInfo.text = #"[text in screen shot]";
[sponsor sizeToFit];
[self.sponsorsScrollView addSubview:sponsor];
}
}
How can I achieve this word wrapping in iOS 7+?
First off I think your textview looks a bit strange. Shouldn't there be a margin on the right side as well? Now it sort of looks like the textview continues to the right. Are the frames correct or have you done something to the textContainerInset?
Anyway to answer your question regarding changing linebreaks: a textview's textContainer property has a lineBreakMode which could be set to NSLineBreakByWordWrapping. I think that should solve it

How do you make a UITextView detect link part in the text and still have userInteractionDisabled?

I have a textView that is layered on a tableView cell. I need the user to click on the tableView row to open a viewController but if the textView has a link it must be clickable and that's it, no copy, select etc.
If I allow user interaction like this:
textView.userInteractionEnabled=NO;
textView.dataDetectorTypes =UIDataDetectorTypeLink;
the didSelectRowAtIndexPath is not called and I understand why but how to achieve this?
EDIT:
What I'm looking for is the ability to specifically identify link and allow user to click on the link to open a browser and disable interaction for the rest of the textView. Also the entire row should be clickable as said above.
Attached is the image of my tableView cell row which shows the link is detected and interaction is also possible but this disables the didSelectRowAtIndexPath inside the textView region.
If you are trying to add a UITextView with links to a cell, and want didSelectRow to be called when the user taps on anything but the link, then you should use hitTest:withEvent:
Swift 3
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// location of the tap
var location = point
location.x -= self.textContainerInset.left
location.y -= self.textContainerInset.top
// find the character that's been tapped
let characterIndex = self.layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
if characterIndex < self.textStorage.length {
// if the character is a link, handle the tap as UITextView normally would
if (self.textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) != nil) {
return self
}
}
// otherwise return nil so the tap goes on to the next receiver
return nil
}
I wrote an article about this with a bit more details.
One possible (and complex) solution would be using UIViews with UITapGestureRecogniser inside your UITextView.
Firstly, you will need to find the NSRange of your link.
Convert NSRange to UITextRange
Use code similar to the following to add a UITapGestureRecogniser right on top of the link-text in your UITextView.
UITextPosition *pos = textView.endOfDocument;// textView ~ UITextView
for (int i = 0; i < words*2 - 1; i++){// *2 since UITextGranularityWord considers a whitespace to be a word
UITextPosition *pos2 = [textView.tokenizer positionFromPosition:pos toBoundary:UITextGranularityWord inDirection:UITextLayoutDirectionLeft];
UITextRange *range = [textView textRangeFromPosition:pos toPosition:pos2];
CGRect resultFrame = [textView firstRectForRange:(UITextRange *)range ];
UIView* tapViewOnText = [[UIView alloc] initWithFrame:resultFrame];
[tapViewOnText addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(targetRoutine)]];
tapViewOnText.tag = 125;
[textView addSubview:tapViewOnText];
[tapViewOnText release];
pos = pos2;
}
What I have done in this code is, got the UITextRange of relevant text, get it's firstRectForRange and added a transparent tap-able UIView right on top of it.
You would have to get the range of your link using some regEx, convert it to UITextRange, and add tap-able UIViews over them. In case, there might be more than one link in a single textView you might add a tag to each view corresponding to their 'link', and open that link in the target method by checking it's tag.
NOTE: If your UITextViews are universally un-editable, you might want to try TTTAttributedLabel instead. That is what I do in my UITableViewCells
Maybe it will be ok for your purposes:
textView.editable=NO;
textView.userInteractionEnabled=YES;
textView.dataDetectorTypes =UIDataDetectorTypeLink;
But in fact UITextView uses UIWebView parts internally, and this way may be slowly in table cells.
I can advice to use CoreText or NSAttributedString's with touches. For example you may read this SO post.
Swift 5, iOS14
Adjusting #Saoud Rizwan Answer because hitTest did not work well for me (it always called the same position multiple times). However here's my solution using UITapGestureRecognizer, where a tap on a link inside a UITextView will be detected.
First configure your Tap event to the UITextView
contentTextView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(textViewTapped(_:))))
The CellTapped implementation:
#objc func cellTapped(_ sender: UITapGestureRecognizer) {
var location = sender.location(in: contentTextView)
location.x -= contentTextView.textContainerInset.left
location.y -= contentTextView.textContainerInset.top
let characterIndex = contentTextView.layoutManager.characterIndex(for: location, in: contentTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
if characterIndex < contentTextView.textStorage.length {
if let url = contentTextView.textStorage.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL {
// The user tapped on a link!
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return
}
}
}
// here: The user tapped somewhere in the textView but not on a link!
}

Strange UItextView behavior setting range within textViewDidChangeSelection

I am getting a strange behavior setting the selectedRange property for a textView within the textViewDidChangeSelection delegate.
My code in viewDidLoad is:
hiddenTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
//[hiddenTextView setHidden:YES];
_hiddenTextViewText=#"ulrd";
hiddenTextView.text = _hiddenTextViewText;
hiddenTextView.delegate = self;
_hiddenTextViewDefaultRange = NSMakeRange(2,0);
hiddenTextView.selectedRange = _hiddenTextViewDefaultRange; //horizontal and vertical center of the textview
[self.view addSubview:hiddenTextView];
[hiddenTextView becomeFirstResponder];
if (_keyboardShown)
[hiddenTextView resignFirstResponder];
I define the textViewDidChangeSelection as follows:
- (void)textViewDidChangeSelection:(UITextView *)textView {
NSLog(#"%lu",(unsigned long)textView.selectedRange.location);
if (textView.selectedRange.location != _hiddenTextViewDefaultRange.location)
{
hiddenTextView.selectedRange = _hiddenTextViewDefaultRange;
}
}
I set a 4 character text and put the selection index in position 2 (middle). The result is that if I press up arrow on the keyboard in simulator NSLog outputs 0 (start of text) and then 2 (reseting the position) which is correct. If I press up again it does the same thing so still correct. Problem is that if I hit up x times I have to hit down equal times before I am able to go to the end of the text (position 4).
I tried resetting the position with a UIButton instead of doing it programmatically and there it works fine. Any ideas?
I managed to overcome the problem. Instead of:
hiddenTextView.selectedRange = _hiddenTextViewDefaultRange;
I used:
dispatch_async(dispatch_get_main_queue(), ^{
textView.selectedRange = hiddenTextViewDefaultRange;
});
...to execute the command async. It worked, however I am not sure why it needed to be like that.

UITextField has trailing whitespace after secureTextEntry toggle

I have a button that toggles between Show/Hide mode (i.e. toggles a UITextField between secureTextEntry NO and YES). The purpose of which is to allow the user to see the password they are entering.
I followed the example (with the highest number of votes) here: UITextField secureTextEntry - works going from YES to NO, but changing back to YES has no effect
However, when I set secureTextEntry to NO, any text that was written there ends up with a space at the end. This does not seem to be a problem when setting secureTextEntry to YES.
For example, if I enter the text "mypassword" while setSecureTextEntry is set to NO, and then switch it to YES, the user will see ********** (10 dots), which is correct. If I setSecureTextEntry to NO, the user will see "mypassword " (with a space at the end, or at least, the cursor moved one space to the right).
Important note: In the debugger, the string value of text appears without the trailing space, like this:
(lldb) expr self.passwordText.text
(NSString *) $0 = 0x1d8450e0 #"mypassword"
I have tried trimming whitespace (per avoid middle whitespace in UITextField), but it has had no effect.
i've just encounter this case and finally solved this problem.
works on Latest iOS SDK, iOS 8.1
First of all, there is no trailing space at all.
The dot(shown in SecureEntry) character and normal character have different width and after you toggle isSecureEntry switch, the cursor didn't refresh it's position.
so i use this workaround to solved this problem.
- (void)toggle
{
NSString *tmpString;
[self.passwordTextField setSecureTextEntry:!self.passwordTextField.isSecureTextEntry];
if (self.passwordTextField.isSecureTextEntry) {
// do stuffs
} else {
// do other stuffs
}
// Workaround to refresh cursor
tmpString = self.passwordTextField.text;
self.passwordTextField.text = #" ";
self.passwordTextField.text = tmpString;
}
Swift 3+
// Workaround to refresh cursor
let currentText: String = self.userPassword.text!
self.userPassword.text = "";
self.userPassword.text = currentText
hope it helps!
PRE-iOS-8.0 (dated solution)... In your button's action method (toggling between secureTextEntry YES and NO), simply set UITextField's text property to its current text value. Although this may seem redundant and a bit like a hack, this will redraw the cursor in the right position. Here's an example of what your button's action method should look like now...
-(void)toggleButtonPressed:(UIButton*)sender
{
// Toggle between secure and not-so-secure entry
self.toggleButton.selected = !self.toggleButton.selected;
self.textfield.secureTextEntry = !self.toggleButton.selected;
// * Redundant (but necessary) line *
[self.textfield setText:self.textfield.text];
}
POST-iOS-8.0... As of iOS 8.0, it appears that UITextField's text setter no longer redraws the cursor when called with a string equal to its current string value. Now, we need to take this a step further and actually change the text value before resetting it again. Replace the above setText: line with something like these lines.
// * Extra redundant (but necessary) lines *
NSString *currentText = self.textfield.text;
[self.textfield setText:#"Arbitrary string..."]; // Change the value
[self.textfield setText:currentText]; // Reset the value
I have a clean solution not going dirty with text property of UITextField.
Wrap them in this style.
[self.passwordTextField resignFirstResponder]; // first resign its first responder.
// change `secureTextEntry` property's value if necessary.
if (self.passwordTextField.secureTextEntry) {
self.passwordTextField.secureTextEntry = NO;
self.passwordEcryptButton.selected = YES;
}else{
self.passwordTextField.secureTextEntry = YES;
self.passwordEcryptButton.selected = NO;
}
[self.passwordTextField becomeFirstResponder]; // finally gain its first responder again.
In order to work around this bug in iOS you can simply do the following (works for any iOS version):
- (IBAction)toggleSecureTextEntry:(UIButton *)button
{
self.textField.secureTextEntry = !self.textField.secureTextEntry;
NSString *originalText = self.textField.text;
self.textField.text = nil;
self.textField.text = originalText;
}
You can fix it like this:
NSString *currentText = self.textfield.text;
self.textfield.text = #"";
self.textfield.text = currentText;
This work for me on iOS 8
if (self.passwordTextField.secureTextEntry) {
// Display password and keep selected text range
UITextRange *selectedTextRange = self.passwordTextField.selectedTextRange;
NSString *password = self.passwordTextField.text;
self.passwordTextField.secureTextEntry = NO;
self.passwordTextField.text = [#"" stringByPaddingToLength:password.length withString:#" " startingAtIndex:0]; // Done for carret redrawing
self.passwordTextField.text = password;
self.passwordTextField.selectedTextRange = selectedTextRange;
}
else {
// Hide password and keep selected text range
UITextRange *selectedTextRange = self.passwordTextField.selectedTextRange;
NSString *password = self.passwordTextField.text;
self.passwordTextField.secureTextEntry = YES;
self.passwordTextField.text = [#"" stringByPaddingToLength:password.length withString:#" " startingAtIndex:0]; // Done for carret redrawing
self.passwordTextField.text = password;
self.passwordTextField.selectedTextRange = selectedTextRange;
}
UITextPosition *beginning = [self.passwordField beginningOfDocument];
[self.passwordField setSelectedTextRange:[self.passwordField textRangeFromPosition:beginning
toPosition:beginning]];
UITextPosition *end = [self.passwordField endOfDocument];
[self.passwordField setSelectedTextRange:[self.passwordField textRangeFromPosition:end
toPosition:end]];
This is what I used for iOS 8
When we change a textfield.secureTextEntry property, the caret position is not updated. To fix this, the code below used to work before IOS 8:
pwdTextField.text = pwdTextField.text
Now it doesn't. It seems IOS 8 detects the new value equals old value and does nothing. So to make it work again we have to actually change the value. Here is the swift version that works for me.
let str = pwdTextField.text
pwdTextField.text = str + " "
pwdTextField.text = str
This is another possibility to solve this issue, where self.passwordText is the UITextField:
if (self.passwordText.isFirstResponder) {
[self.passwordText resignFirstResponder];
[self.passwordText becomeFirstResponder];
}
It appears that the second solution in the referenced link, when implemented, has the desired behavior of not adding an extra space:
https://stackoverflow.com/a/8495888/738190
This Works in my case
BOOL wasFirstResponder = [self.passwordTextField isFirstResponder];
if([self.passwordTextField isSecureTextEntry])
{
//This three lines are key
self.passwordTextField.delegate = nil;
[self.passwordTextField resignFirstResponder];
self.passwordTextField.delegate = self;
}
[self.passwordTextField setSecureTextEntry: !self.passwordTextField.isSecureTextEntry];
if(wasFirstResponder)
[self.passwordTextField becomeFirstResponder];
Swift UITextField extension:
extension UITextField {
func toggleSecureEntry() {
let wasFirstResponder = isFirstResponder
if wasFirstResponder { resignFirstResponder() }
isSecureTextEntry.toggle()
if wasFirstResponder { becomeFirstResponder() }
}
}
Setting textField.text solution also works in some situations but not for my need (Custom font with two text fields. Caused font changes and glitches on runtime.) Adding here too.
func toggleSecureEntry() {
isSecureTextEntry.toggle()
let originalText = text
text = nil
text = originalText
}
To get the cursor to reposition correctly, setting the font attributes seemed to do the trick for me.
// Hack to update cursor position
self.passwordTf.defaultTextAttributes = #{NSFontAttributeName: textFieldFont, NSForegroundColorAttributeName: textFieldColor};
// Change secure entry
self.passwordTf.secureTextEntry = !self.passwordTf.secureTextEntry;
Tested on iOS8, iOS9.
Hope it helps!
Everytime the text is set in the UITextField, the cursor postition is updated
So I used this code
partial void btnShowPassword_ToutchUpInside (UIButton sender)
{
if (TxtPassword.SecureTextEntry == true) {
TxtPassword.SecureTextEntry = false;
TxtPassword.Text = TxtPassword.Text;
} else {
TxtPassword.SecureTextEntry = true;
}
}
Here is the solution:
- (void)showHidePassword:(UIButton *)sender {
EDSignUpCell *cell = [self.signUpTblView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]];
if(!TRIM_SPACE(cell.cellTextField.text).length) {return;}
[cell.showHidePasswordBtn setSelected:!cell.showHidePasswordBtn.isSelected];
cell.cellTextField.secureTextEntry = cell.showHidePasswordBtn.isSelected;
[cell.cellTextField setText:cell.cellTextField.text];
[cell.cellTextField becomeFirstResponder];
}
I'm using this, Works fine.
[self.yourTextField becomeFirstResponder];
Swift 4
Bug is on radar, there is explanation of workaround also: http://www.openradar.me/38465011
Here is cut of temporary workaround how to natively update caret (cursor) position.
// update caret position
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
let (beginning, end) = (self.beginningOfDocument, self.endOfDocument)
self.selectedTextRange = self.textRange(from: beginning, to: end)
self.selectedTextRange = self.textRange(from: end, to: end)
}
I had a similar issue and realized it was because I was updating the text before setting the secureTextEntry property. It makes sense that the textField would draw out the caret at the location it'd be at if it were using secureTextEntry.
I did not read the entire problem nor did I visit the solution linked by OP, but in case someone else has the same issue as me:
Try updating your text after setting the secureTextEntry property.

Hide the cursor of a UITextField

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?
Simply subclass UITextField and override caretRectForPosition
- (CGRect)caretRectForPosition:(UITextPosition *)position
{
return CGRectZero;
}
As of iOS 7 you can now just set the tintColor = [UIColor clearColor] on the textField and the caret will disappear.
You can just clear the textfield's tintColor
self.textField.tintColor = [UIColor clearColor];
Swift 3.0
self.textField.tintColor = .clear
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/
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.
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];
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;
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
}
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.
set the tintColor to Clear Color
textfield.tintColor = [UIColor clearColor];
and you can also set from the interface builder
If you want to hide cursor, you can easily use this! It worked for me..
[[textField valueForKey:#"textInputTraits"] setValue:[UIColor clearColor] forKey:#"insertionPointColor"]
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?
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;
}
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.
If you like cleaner = less code, use the interface builder:
(Attributes inspector, view section.)
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.
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

Resources