UiTextView move to next character error with emoji - ios

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);

Related

ios textView.selectedTextRange giving wrong range for textview containing Emojis

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.

How to wrap text without showing hyphenation characters (while using the same word split rules)?

I am trying to achieve word wrapping with hyphenation but do not want to see the hyphenation character in the uitextview.
In viewDidLoad;
textFieldParagraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping
textFieldParagraphStyle.alignment = NSTextAlignment.Center
textFieldParagraphStyle.hyphenationFactor = 1.0
Everything works well but is it possible to get rid of hyphenation character with preserving the correct syllable wrapping?
To illustrate, suppose we have the word understand. If I use word wrapping with hypenation factor set 1.0, the uitextview divided the word like;
under-
stand
What I want to achieve is;
under
stand
Here is a code snippet. Read about CFStringGetHyphenationLocationBeforeIndex here to understand how the method works. Hope it helps. If your text view width allows to have multiple words in a line, then probably you should define at what syllable in the word you should make hyphenation, you can change location and limitRange to get other hyphens, not only in the word's "middle", like in my example.
- (NSString*)hyphenatedWordFromWordString:(NSString*)word
{
CFStringRef strRef = (__bridge CFStringRef)word;
CFIndex location = _textView.text.length;
CFRange limitRange = CFRangeMake(0, location);
CFOptionFlags flags = 0;
CFLocaleRef locale = (__bridge CFLocaleRef)[NSLocale currentLocale];
CFIndex index = CFStringGetHyphenationLocationBeforeIndex(strRef, location, limitRange, flags, locale, NULL);
if (index != kCFNotFound) {
word = [NSString stringWithFormat:#"%#\n%#", [word substringToIndex:index], [word substringFromIndex:index]];
}
return word;
}

Detect new-line character for auto enter new line text on UITextView on iOS

When typing on UITextView on iOS app, if text is beyond the width of UITextView, UITextView will automatically enter new line and continue typing, but the problem is when get the text out, it's still just one-line text.
But when I get the text from this textview
NSString* newtext = textview.text;
The value of newtext will be "AAAAAAAAAAAAAAAAAAAAAAAAAAAAOMMM" (all is one-line) but I'm expected it will be "AAAAAAAAAAAAAAAAAAAAAAAAAAAAO\nMMM" (notice the '\n' character notify the new line)
Is there any way to do that
The UITextView doesn't automatically enter a newline character once its text reaches the end of the line -- it simply wraps around with a line break. But if you want a string representation of the UITextView text which includes newline characters to indicate the various line breaks, try this:
// This method takes in the `UITextView` and returns the string
// representation which includes the newline characters
- (NSString*)textViewWithNewLines:(UITextView*)textView {
// Create a variable to store the new string
NSString *stringWithNewlines = #"";
// Get the height of line one and store it in
// a variable representing the height of the current
// line
int currentLineHeight = textView.font.lineHeight;
// Go through the text view character by character
for (int i = 0 ; i < textView.text.length ; i ++) {
// Place the cursor at the current character
textView.selectedRange = NSMakeRange(i, 0);
// And use the cursor position to help calculate
// the current line height within the text view
CGPoint cursorPosition = [textView caretRectForPosition:textView.selectedTextRange.start].origin;
// If the y value of the cursor is greater than
// the currentLineHeight, we've moved onto the next line
if (cursorPosition.y > currentLineHeight) {
// Increment the currentLineHeight such that it's
// set to the height of the next line
currentLineHeight += textView.font.lineHeight;
// If there isn't a user inputted newline already,
// add a newline character to reflect the new line.
if (textView.text.length > i - 1 &&
[textView.text characterAtIndex:i-1] != '\n') {
stringWithNewlines = [stringWithNewlines stringByAppendingString:#"\n"];
}
// Then add the character to the stringWithNewlines variable
stringWithNewlines = [stringWithNewlines stringByAppendingString:[textView.text substringWithRange:NSMakeRange(i, 1)]];
} else {
// If the character is still on the "current line" simply
// add the character to the stringWithNewlines variable
stringWithNewlines = [stringWithNewlines stringByAppendingString:[textView.text substringWithRange:NSMakeRange(i, 1)]];
}
}
// Return the string representation of the text view
// now containing the newlines
return stringWithNewlines;
}

Text string with EMOJI causing issues with NSRange

I am using TTTAttributedLabel to apply formatting to text, however it seems to crash because I am trying to apply formatting to a range which includes emoji. Example:
NSString *text = #"#user1234 ๐Ÿบ๐Ÿบ #hashtag"; // text.length reported as 22 by NSLog as each emoji is 2 chars in length
cell.textLabel.text = text;
int length = 8;
int start = 13;
NSRange *range = NSMakeRange(start, length);
if (!NSEqualRanges(range, NSMakeRange(NSNotFound, 0))) {
// apply formatting to TTTAttributedLabel
[cell.textLabel addLinkToURL:[NSURL URLWithString:[NSString stringWithFormat:#"someaction://hashtag/%#", [cell.textLabel.text substringWithRange:range]]] withRange:range];
}
Note: I am passed the NSRange values from an API, as well as the text string.
In the above I am attempting to apply formatting to #hashtag. Normally this works fine, but because I have emoji involved in the string, I believe the range identified is attempting to format the emoji, as they are actually UTF values, which in TTTAttributedLabel causes a crash (it actually hangs with no crash, but...)
Strangely, it works fine if there is 1 emoji, but breaks if there are 2.
Can anyone help me figure out what to do here?
The problem is that any Unicode character in your string with a Unicode value of \U10000 or higher will appears as two characters in NSString.
Since you want to format the hashtag, you should use more dynamic ways to obtain the start and length values. Use NSString rangeOfString to find the location of the # character. Use that results and the string's length to get the needed length.
NSString *text = #"#user1234 ๐Ÿบ๐Ÿบ #hashtag"; // text.length reported as 22 by NSLog as each emoji is 2 chars in length
cell.textLabel.text = text;
NSUInteger start = [text rangeOfString:#"#"];
if (start != NSNotFound) {
NSUInteger length = text.length - start;
NSRange *range = NSMakeRange(start, length);
// apply formatting to TTTAttributedLabel
[cell.textLabel addLinkToURL:[NSURL URLWithString:[NSString stringWithFormat:#"someaction://hashtag/%#", [cell.textLabel.text substringWithRange:range]]] withRange:range];
}
I assume this is from the Twitter API, and you are trying to use the entities dictionary they return. I have just been writing code to support handling those ranges along with NSString's version of the range of a string.
My approach was to "fix" the entities dictionary that Twitter return to cope with the extra characters. I can't share code, for various reasons, but this is what I did:
Make a deep mutable copy of the entities dictionary.
Loop through the entire range of the string, unichar by unichar, doing this:
Check if the unichar is in the surrogate pair range (0xd800 -> 0xdfff).
If it is a surrogate pair codepoint, then go through all the entries in the entities dictionary and shift the indices by 1 if they are greater than the current location in the string (in terms of unichars). Then increment the loop counter by 1 to skip the partner of this surrogate pair as it's been handled now.
If it's not a surrogate pair, do nothing.
Loop through all entities and check that none of them overrun the end of the string. They shouldn't, but just incase. I found some cases where Twitter returned duff data.
I hope that helps! I also hope that one day I can open source this code as I think it would be incredibly useful!

Which characters does NSLineBreakByWordWrapping break on?

Short version
From the docs:
NSLineBreakByWordWrapping
Wrapping occurs at word boundaries, unless the word itself doesnโ€™t fit on a single line.
What is the set of all word boundary characters?
Longer version
I have a set of UILabels, which contain text, sometimes including URLs. I need to know the exact location (frame, not range) of the URLs so that I can make them tappable. My math mostly works, but I had to build in a test for certain characters:
// This code only reached if the URL is longer than the available width
NSString *theText = // A string containing an HTTP/HTTPS URL
NSCharacterSet *breakChars = [NSCharacterSet characterSetWithCharactersInString:#"?-"];
NSString *charsInRemaininsSpace = // NSString with remaining text on this line
NSUInteger breakIndex = NSNotFound;
if (charsInRemaininsSpace)
breakIndex = [charsInRemaininsSpace rangeOfCharacterFromSet:breakChars
options:NSBackwardsSearch].location;
if (breakIndex != NSNotFound && breakIndex != theText.length-1) {
// There is a breakable char in the middle, so draw a URL through that, then break
// ...
} else {
// There is no breakable char (or it's at the end), so start this word on a new line
// ...
}
The characters in my NSCharacterSet are just ? and -, which I discovered NSLineBreakByWordWrapping breaks on. It does not break on some other characters I see in URLs like % and =. Is there a complete list of characters I should be breaking on?
I recommend using "not alphanumeric" as your test.
[[NSCharacterSet alphanumerCharacterSet] invertedSet]
That said, if you're doing a lot of this, you may want to consider using CTFramesetter to do your layout instead of UILabel. Then you can use CTRunGetImageBounds to calculate actual rects. You wouldn't have to calculate your word breaks, since CTFrame will already have done this for you (and it would be guaranteed to be the same algorithm).

Resources