How to highlight text in line containing emojis in Swift?

I have the following function to find and highlight hashtags or mentions (@ or #) in UILabel :

 class func addLinkAttribute(pattern: String, toText text: String, withAttributeName attributeName : String, toAttributedString attributedString :NSMutableAttributedString, withLinkAttributes linkAttributes: [NSObject : AnyObject]) { var error: NSError? if let regex = NSRegularExpression(pattern: pattern, options:.CaseInsensitive, error: &error) { regex.enumerateMatchesInString(text, options: .allZeros, range: NSMakeRange(0, count(text))) { result, flags, stop in let range = result.range let start = advance(text.startIndex, range.location) let end = advance(start, range.length) let foundText = text.substringWithRange(Range<String.Index>(start: start,end: end)) var linkAttributesWithName = linkAttributes linkAttributesWithName[attributeName] = foundText attributedString.addAttributes(linkAttributesWithName, range: range) } } } 

If you pass hashtag (#)(\\w+) or mention the pattern (@)(\\w+) , the code works fine, but if the text contains Emoji, the range is shifted by the number of emojis preceding it:

enter image description here

I know that Swift treats strings differently in Objective-C, since count(string) and count(string.utf16) give me different results, but I can’t tell how to take this into account when using a regular expression.

I could just check the difference between the two samples and shift the range, but that seems wrong and hacked to me. There must be another way.

+6
source share
1 answer

Just as in Swift extract regex matches , a possible solution is to convert the given Swift String to NSString and apply the NSRange returned by enumerateMatchesInString() to this NSString :

 class func addLinkAttribute(pattern: String, toText text: String, withAttributeName attributeName : String, toAttributedString attributedString :NSMutableAttributedString, withLinkAttributes linkAttributes: [NSObject : AnyObject]) { let nsText = text as NSString var error: NSError? if let regex = NSRegularExpression(pattern: pattern, options:.CaseInsensitive, error: &error) { regex.enumerateMatchesInString(text, options: .allZeros, range: NSMakeRange(0, nsText.length)) { result, _, _ in let range = result.range let foundText = nsText.substringWithRange(range) var linkAttributesWithName = linkAttributes linkAttributesWithName[attributeName] = foundText attributedString.addAttributes(linkAttributesWithName, range: range) } } } 

(Alternative solution.) You can convert NSRange to Range<String.Index> without intermediate conversion to NSString . Via

 extension String { func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? { let utf16start = self.utf16.startIndex if let from = String.Index(self.utf16.startIndex + nsRange.location, within: self), let to = String.Index(self.utf16.startIndex + nsRange.location + nsRange.length, within: self) { return from ..< to } return nil } } 

from fooobar.com/questions/22448 / ... , your code can be written as

 class func addLinkAttribute(pattern: String, toText text: String, withAttributeName attributeName : String, toAttributedString attributedString :NSMutableAttributedString, withLinkAttributes linkAttributes: [NSObject : AnyObject]) { var error: NSError? if let regex = NSRegularExpression(pattern: pattern, options:.CaseInsensitive, error: &error) { regex.enumerateMatchesInString(text, options: .allZeros, range: NSMakeRange(0, count(text.utf16))) { result, _, _ in let nsRange = result.range if let strRange = text.rangeFromNSRange(nsRange) { let foundText = text.substringWithRange(strRange) var linkAttributesWithName = linkAttributes linkAttributesWithName[attributeName] = foundText attributedString.addAttributes(linkAttributesWithName, range: nsRange) } } } } 

and this should also work correctly for all types of extended grapheme clusters (Emojis, regional indicators, etc.)

+8
source

Source: https://habr.com/ru/post/987760/


All Articles