ios textView.selectedTextRange giving wrong range for textview containing Emojis - ios

My function wants to get cursor current position in textview, but when i have string "😳 😱 😨 " (cursor is at last position)in textview and it return me range (0,6) and current index as 6, expected position is 3,Please let me know if any way to get cursor position
func getcurserforTextView(textView : UITextView ) -> Int {
var cursorPosition = 0
if let selectedRange = textView.selectedTextRange {
cursorPosition = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start)
}
return cursorPosition
}

The selectedRange and offset(from:to:) values are based on the UTF-16 characters for the text in the text view. So the result of 6 for that string is correct since that string contains 6 characters when using the UTF-16 character encoding.
So depending on what you are doing with the obtained cursor position, you may need to convert that UTF-16 offset to a "normal" String index.
Please see Converting scanLocation from utf16 units to character index in NSScanner (Swift) which covers a similar situation and a way to perform the conversion.

Related

UITextView.text with emoji setting: cursorPosition is not correct

Here, str contains emojis and set cursorPosition is not correct:
let cursorPosition = str.characters.count
let cursorRange = NSRange(location: cursorPosition, length: 0)
textInputView.selectedRange = cursorRange
textInputView.scrollRangeToVisible(cursorRange)
It is look like you want to put cursor at the last of textView. Try like this way.
textInputView.becomeFirstResponder()
let cursorPosition = str.utf16.count
let cursorRange = NSRange(location: cursorPosition, length: 0)
textInputView.selectedRange = cursorRange
textInputView.scrollRangeToVisible(cursorRange)
This is an old question, but I'll add some points if I ever run into this problem again:
Nirav D answer works if you just want to go to the end of the string.
This happens because while emojis count as a single character position in strings, they count as more than one position in the textfield as the cursor position uses the utf16 string position. That's why the cursor does not go to the end of the textfield with your code.
If you want to move the cursor to any point other than the end, use the string.utf16 corresponding position.

UITextView will shorten text?

How to know wether UITextView will shorten text because lack of space? I know I can calculate with boundingRectWithSize and sizeThatFit, but what if exclusionPaths of the UITextView is changed. I want lay out string in a polygon, and increase polygon size, until string laid out without shortening. Any idea, how can get a bool return, wether current setup will shorten?
self.tv.textContainer.exclusionPaths = myArrayOfBezierPaths;
func isSizeFitForTextView() -> Bool{
let layoutManager = self.textView.layoutManager
let glyphIndex = layoutManager.glyphIndexForCharacter(at:( self.textView.text as NSString).length)
let range = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: glyphIndex)
return range.location != NSNotFound
}

Move Cursor One Word(including language like Chinese) at a Time in UTextView

I have read this,but it can only work well in English for it just use white-space and something like NewlineCharacterSet as separator.
I want to add a left arrow and a right arrow in the accessory input view to move the cursor in UITextView by words.
And I am wondering how to support that feature for some Asian languages like Chinese
PS:I will added an example that CFStringTokenizer failed to work with when there are both English Characters and Chinese characters
test string:
Happy Christmas! Text view test 云存储容器测试开心 yeap
the expected boundaries:
Happy/ Christmas!/ Text/ view/ test/ 云/存储/容器/测试/开心/ yeap/
the boundaries show in reality:
Happy/ Christmas!/ Text/ view/ test/ 云存储容器测试开心/ yeap/
I don't speak Chinese, but according to the documentation,
CFStringTokenizer is able to find word boundaries in many languages,
including Asian languages.
The following code shows how to advance from one word ("world" at position 6)
to the next word ("This" at position 13):
// Test string.
NSString *string = #"Hello world. This is great.";
// Create tokenizer
CFStringTokenizerRef tokenizer = CFStringTokenizerCreate(NULL,
(__bridge CFStringRef)(string),
CFRangeMake(0, [string length]),
kCFStringTokenizerUnitWordBoundary,
CFLocaleCopyCurrent());
// Start with a position that is inside the word "world".
CFIndex position = 6;
// Goto current token ("world")
CFStringTokenizerTokenType tokenType;
tokenType = CFStringTokenizerGoToTokenAtIndex(tokenizer, position);
if (tokenType != kCFStringTokenizerTokenNone) {
// Advance to next "normal" token:
tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer);
while (tokenType != kCFStringTokenizerTokenNone && tokenType != kCFStringTokenizerTokenNormal) {
tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer);
}
if (tokenType != kCFStringTokenizerTokenNone) {
// Get the location of next token in the string:
CFRange range = CFStringTokenizerGetCurrentTokenRange(tokenizer);
position = range.location;
NSLog(#"%ld", position);
// Output: 13 = position of the word "This"
}
}
There is no CFStringTokenizerAdvanceToPreviousToken() function, so to move to
the previous word you have to start at the beginning of the string and advance forward.
Finnally I use UITextInputTokenizer to realized the function

UiTextView move to next character error with emoji

In a UiTextView i have a function to jump to the next character after the caret:
textView.selectedRange = NSMakeRange(textView.selectedRange.location+1,0);
When i have emoticons it doesn't work, i have to it twice.
I was wondering if i can check if the next character after the caret if a special character so i move 2 instead of 1 in caret location.
And when i try to get the NSString of thaT range it doesn't give anything or error
NSRange myRange = NSMakeRange(textView.selectedRange.location,1);
[textView.text substringWithRange:myRange]
Any help?
Use the NSString rangeOfComposedCharacterSequenceAtIndex: method. For most characters this will return a range with a length of 1. But for characters with a Unicode value of U+10000 or more, this will give a range with a length of 2.
This will move the caret to the next character:
NSRange nextRange = [textView.text rangeOfComposedCharacterSequenceAtIndex:textView.selectedRange.location];
textView.selectedRange = NSMakeRange(nextRange.location + nextRange.length, 0);

How to get a selected range of text from a UITextView or UITextField

I am trying to get the selected range of text from a UITextView (and or UITextField) so that I can edit the selected text, or modify an attributed string. The method below is triggered when I make a selection, but the code in the method returns null values.
- (void)textViewDidChangeSelection:(UITextView *)textView {
UITextRange *selectedRange = [textField selectedTextRange];
NSLog(#"Start: %# <> End: %#", selectedRange.start, selectedRange.end);
}
You can try this,
- (void)textViewDidChangeSelection:(UITextView *)textView {
UITextRange *selectedRange = [textView selectedTextRange];
NSString *selectedText = [textView textInRange:selectedRange];
}
Swift
First get the selected text range, and then use that range to get the actual text:
if let textRange = myTextView.selectedTextRange {
let selectedText = myTextView.text(in: textRange)
// ...
}
Notes:
Selecting text from a UITextField is done in the same way.
The range is a UITextRange, not an NSRange. This allows for proper selection of things like emoji and extended grapheme clusters. See this answer for related details about this.
Swift 5.0
here is how I selecte file name Panda from Panda.txt
func textFieldDidBeginEditing(_ textField: UITextField) {
// if textField.text is `Panda.txt`, offset will be 3
let offset = String(textField.text!.split(separator: ".").last!).length
let from = textField.position(from: textField.beginningOfDocument, offset: 0)
let to = textField.position(from: textField.beginningOfDocument,
offset:textField.text!.length - (offset+1) )
//now `Panda` will be selected
textField.selectedTextRange = textField.textRange(from: from!, to: to!)
//note: unwrap with `!` is not recommended, text here is 100% not null,so it's safe
}
Swift 4.1
In your UITextView Extension put the below code in a function, and use that in your controller:
You can call this method with your textView instance in the SelectionDidChange delegate method from your view-Controller. Better to wrap this function call with condition textView.selectedRange.length > 0, to get some text...
let begin = self.beginningOfDocument
let start = self.position(from: begin, offset: selectedRange.location)
let end = self.position(from: position(from: start!, offset: 0)!, offset: selectedRange.length)
let txtRange = self.textRange(from: start!, to: end!)
let txt = self.text(in: txtRange!)
print("Sel Text is \(String(describing: txt))")
We can not use the optional binding to store the selected range, instead you can declare an optional for nsrange type, then use the if- let ... thing.
TextInputComponent has a property to get the selected text range.
let range = textView.selectedRange
Then you can use range.location, range.length values to change attributes of text in the container, etc...

Resources