Getting cursor position in a UITextView on the iPhone? - iphone

We have a UITextView in our iPhone app which is editable. We need to insert some text at the cursor location when the users presses some toolbar buttons but can't seem to find a documented (or undocumented) method of finding the current location of the cursor.
Does anybody have any ideas or has anybody else achieved anything similar?

Like drewh said, you can use UITextView's selectedRange to return the insertion point. The length of this range is always zero. The example below shows how to it.
NSString *contentsToAdd = #"some string";
NSRange cursorPosition = [tf selectedRange];
NSMutableString *tfContent = [[NSMutableString alloc] initWithString:[tf text]];
[tfContent insertString:contentsToAdd atIndex:cursorPosition.location];
[theTextField setText:tfContent];
[tfContent release];

Swift 4:
// lets be safe, thus if-let
if let cursorPosition = textView.selectedTextRange?.start {
// cursorPosition is a UITextPosition object describing position in the text
// if you want to know its position in textView in points:
let caretPositionRect = textView.caretRect(for: cursorPosition)
}
We simply use textView.selectedTextRange to get selected text range and cursor position is at its start position.

Use UITextView selectedRange property to find the insertion point when the text view is first responder. Otherwise, when the view is not in focus, this property returns NSNotFound. If you need to know the cursor position in that case, consider subclassing UITextView and overriding canResignFirstResponder method, where you can store cursor position to a member variable.

Have you tried UITextView.selectedRange? It returns an NSRange, whose location element should tell you, where the cursor is.

Related

Detecting tapped character. UITextView characterRangeAtPoint always returns nil

I'm trying to determine, which particular character in UITextView has been tapped. I tried to use characterRangeAtPoint: method. But it always returns nil, wherever in the UITextView I tap.
I even wrote and run the following piece of code:
for (int x = 0; x<1000; x++) {
pos.x = x;
for (int y = 0; y<1000; y++) {
pos.y = y;
UITextRange * range = [textView characterRangeAtPoint:pos];
if (range!=nil) {
NSLog(#"x: %f, y: %f", pos.x, pos.y);
}
}
}
Well, it never reaches the NSLog string.
What am I doing wrong?
This is an iOS 7 bug. characterRangeAtPoint: always returns nil, unless some text has been selected at least once beforehand.
As a workaround, you could call setSelectedRange: or setSelectedTextRange: beforehand, but then the textView would highlight your selection. I came up with the following solution:
[textView select:textView];
[textView setSelectedTextRange:nil];
After you call these two methods, characterRangeAtPoint: will start working as expected.
Note that you just need to call this once after textView has been initialized.
EDIT: my answer was edited from "unless some text has been selected beforehand" to "unless some text is currently selected". This is wrong: text doesn't need to be currently selected, it just has to be selected once. All subsequent calls to setSelectedRange: will then be successful, as stated in my note. Edited back for further clarity.
Your UITextView must be selectable. In storyboard, check the "Selectable" checkbox.
In code,
textView.selectable = YES;
This is also required for the -closestPositionToPoint: method on UITextView.
Use shouldChangeCharactersInRange method instead of characterRangeAtPoint.
And use your If condition inplace of loop statement.
Is your TextView user-editable?
If YES, then try the texViewDidChangeSelection method. It is invoked every time the caret(cursor) is moved in a textView. Then get the cursor position by textView.selectedRange.location to point to the index, of the caret.
My assumption here is that you want the location of the character with respect to the number of character, and not co-ordinates in 2D.

Insert string at location Objective-C

I'm making an app the has 'hotkeys' in it and when you tap the hotkey it should insert a character at the location you are typing at.
I'm using a UITextView with editing on.
What I want do do is insert the text right after the blue cursor.
Is this possible?
You want to use the insertText: method of UITextView, which is declared in its implementation of the UIKeyInput protocol (which is a super-protocol of UITextInput, which UITextView implements.)
You can get the location of the cursor by using
int position = txtView.selectedRange.location;
Then you will need to get the string from the text view and insert your own string
NSMutableString *str = [txtView.text mutableCopy];
[str insertString:#"YourString" atIndex:position];
txtView.text = str;
This may scroll the text view and lose your current cursor, if you dont want to lose it, you will need to subclass UITextView and do some work inside the drawRect

how to insert text at any cursor position in uitextview?

i want to implement Code by which i can start to insert text at any position of cursor in UITextView in iphone sdk
any idea?
thank you in advance..
i refereed this link: iPhone SDK: How to create a UITextView that inserts text where you tap?
But not Getting it.
Dan's answer is manually changing the text. It's not playing well with UITextView's UndoManager.
Actually it's very easy to insert text with UITextInput protocol API, which is supported by UITextView and UITextField.
[textView replaceRange:textView.selectedTextRange withText:insertingString];
Note: It's selectedTextRange in UITextInput protocol, rather than selectedRange
This is what I use with a custom keyboard, seems to work ok, there may be a cleaner approach, not sure.
NSRange range = myTextView.selectedRange;
NSString * firstHalfString = [myTextView.text substringToIndex:range.location];
NSString * secondHalfString = [myTextView.text substringFromIndex: range.location];
myTextView.scrollEnabled = NO; // turn off scrolling
NSString * insertingString = [NSString stringWithFormat:#"your string value here"];
myTextView.text = [NSString stringWithFormat: #"%#%#%#",
firstHalfString,
insertingString,
secondHalfString];
range.location += [insertingString length];
myTextView.selectedRange = range;
myTextView.scrollEnabled = YES; // turn scrolling back on.
The simplest way (but it won't replace selected text) is to use the insertText: method:
[textView insertText:#"some text you want to insert"];
UITextView conforms to UITextInput which itself conforms to UIKeyInput.
Here is the Glorfindel's answer in Swift3. The text its inserting here it pulls out of the clipboard.
if let textRange = myTextView.selectedTextRange {
myTextView.replace(textRange, withText:UIPasteboard.general.string!)
}

UITextView.text property doesn't update first time, and then clips or doesn't display when it does

I'm trying to get the contents from a dictionary into a UITextView. The dictionary contains molecular masses paired with percentages, for example:
24 -> 98
25 -> 1.9
26 -> 0.1
I have an NSArray containing the keys from the dictionary, sorted in ascending order. So, here is my code to generate the string to set as the textField.text property:
-(void)detailIsotopes:(NSMutableDictionary *)isotopes withOrder:(NSMutableArray *)order{
NSMutableString *detailString = [[NSMutableString alloc] init];
for (NSNumber *mass in order){
[detailString appendString:[NSString stringWithFormat:#"%d: %f\n", [mass integerValue], [[isotopes valueForKey:[NSString stringWithFormat:#"%#", mass]] floatValue]]];
}
NSLog(#"%#", detailString);
textField.text = detailString;
[detailString release];
}
This should create a string looking like this:
24: 89
25: 1.9
26: 0.1
For some reason, this method never does anything the first time it runs. I see the NSLog output, which outputs the correct string. However, the contents of the UITextView don't change: they stay as 'Lorem ipsum dolor sit amet...' from Interface Builder. If I run the method again, it works, sort of.
The UITextView displays some of the text, and then just cuts off half way through a line, leaving only the tops of the characters. If I delete the contents above the half line, the other lines pull up from under the divide: the contents are there, they just stop being shown, if you understand what I mean. This appears to go away if I enable paging in the view. If I do that, then the line isn't truncated, but the UITextView just stops showing any content after some point, although the scroll bar indicates that there is more to go (which there is).
The view containing the UITextView is not visible when the contents is set, if that makes a difference. A separate view controller generates the NSMutableDictionary and NSMutableArray and sends them to its delegate, which then sends them to the view which should display the UITextField and has the detailIsotopes: withOrder: method. The two can be swapped between with an info button.
Does anyone understand why these things are happening?
Thanks for any advice you can give!
First of all, I don't think you need to allocate and release your NSMutableString here. Simply initialize one using [NSMutableString string] which creates an empty string you can modify and don't need to explicitly release.
Second thing, you seem to store masses in NSStrings in your NSDictionnary, why do you use their integerValue method for stringWithFormat (with %d modifier), instead of using them as is? (the stringWithFormat modifier for NSStrings is %#)
Also, you talk about a UITextView at start, then about a UITextField, are you sure you did not make a mistake somewhere? I guess the receiving object for your formatted NSString should rather be the UITextView (if you have both a textField and textView).
If it's not about this, maybe you are calling detailIsotopes too early and the textView it's created yet. Try to NSLog its address and see if it's nil the first time. It could be the case if you use Interface Builder and your UITextField is an ib outlet. If you do, then you could store your dictionary and array in the viewController, and set the textField in the viewController's viewDidLoad method. Or call detailIsotopes after you've displayed the view, I guess that's up to you.
About the truncated text, I think that's because UITextView doesn't resize itself automatically, so it keeps the height you originally set. What I usually do is this:
CGRect frame = textView.frame;
frame.size.height = textView.contentSize; // you can adjust this to leave some space at the end
textView.frame = frame;
This will set the textView height to the content (the text) height.
Also note that if your textView is supposed to display the whole text, you can set its scrollingEnabled property to FALSE so it never allows scrolling.
Hope that helps.

Cursor location in a uitextfield

im using customkeyboard in my controller. but how to get the cursor location in the uitextfiled. my requirement is to enter a charecter in a desired location of textfield. is it possible?
Let's assume that you code is in a method on an object where self.textField is the UITextField in question.
You can find the current position of the cursor/selection with:
NSRange range = self.textField.selectedTextRange;
If the user has not selected text the range.length will be 0, indicating that it is just a cursor. Unfortunately this property does not appear to be KVO compliant, so there is no efficient way to be informed when it changes, however this should not be a problem in your case because you probably really only care about it when you are responding to user interaction with your custom keyboard.
You can then use (assuming newText holds the input from your custom keyboard).
[self.textField replaceRange:range withText:newText];
If you need to subsequently adjust the cursor/selection you can use:
self.textField.selectedTextRange = newRange;
For example, you may want to position the cursor after the text you inserted.
UPDATE:
In my original answer I failed to notice that I was leveraging a category I had added to UITextView:
- (void)setSelectedRange:(NSRange)selectedRange
{
UITextPosition* from = [self positionFromPosition:self.beginningOfDocument offset:selectedRange.location];
UITextPosition* to = [self positionFromPosition:from offset:selectedRange.length];
self.selectedTextRange = [self textRangeFromPosition:from toPosition:to];
}
- (NSRange)selectedRange
{
UITextRange* range = self.selectedTextRange;
NSInteger location = [self offsetFromPosition:self.beginningOfDocument toPosition:range.start];
NSInteger length = [self offsetFromPosition:range.start toPosition:range.end];
NSAssert(location >= 0, #"Location is valid.");
NSAssert(length >= 0, #"Length is valid.");
return NSMakeRange(location, length);
}
Then replace use self.textField.selectedRange instead of self.textField.selectedTextRange and proceed as I described.
Thanks to omz for pointing out my error.
Of course, you can work directly with UITextRange but, at least in my case, this proved to be rather ungainly.
The answer is that you can't get the current cursor location for all types of editing that can be done with the textfield. You can insert characters at the cursor with [textField paste], but the user can move the cursor, select and modify text, without a way to get notified where the cursor ended up.
You can temporarily paste a special character and search its position in the string, remove it, and then add the character you want to have there.
Swift
Get the cursor location:
if let selectedRange = textField.selectedTextRange {
let cursorPosition = textField.offsetFromPosition(textField.beginningOfDocument, toPosition: selectedRange.start)
}
Enter text at some arbitrary location:
let arbitraryValue: Int = 5
if let newPosition = textField.positionFromPosition(textField.beginningOfDocument, inDirection: UITextLayoutDirection.Right, offset: arbitraryValue) {
textField.selectedTextRange = textField.textRangeFromPosition(newPosition, toPosition: newPosition)
textField.insertText("Hello")
}
My full answer is here.