Unify emoji replacement code

This commit is contained in:
Shadowfacts 2021-11-07 13:11:49 -05:00
parent e7d9e3780e
commit 1c0291b1dd
5 changed files with 31 additions and 50 deletions

View File

@ -19,10 +19,15 @@ protocol BaseEmojiLabel: AnyObject {
}
extension BaseEmojiLabel {
func replaceEmojis(in string: String, emojis: [Emoji], identifier: String, completion: @escaping (NSAttributedString) -> Void) {
let matches = emojiRegex.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))
func replaceEmojis(in attributedString: NSAttributedString, emojis: [Emoji], identifier: String?, completion: @escaping (_ attributedString: NSAttributedString, _ didReplaceEmojis: Bool) -> Void) {
guard !emojis.isEmpty else {
completion(attributedString, false)
return
}
let matches = emojiRegex.matches(in: attributedString.string, options: [], range: NSRange(location: 0, length: attributedString.length))
guard !matches.isEmpty else {
completion(NSAttributedString(string: string))
completion(attributedString, false)
return
}
@ -33,8 +38,8 @@ extension BaseEmojiLabel {
for emoji in emojis {
// only make requests for emojis that are present in the text to avoid making unnecessary network requests
guard matches.contains(where: { (match) in
let matchShortcode = (string as NSString).substring(with: match.range(at: 1))
guard matches.contains(where: { (match) -> Bool in
let matchShortcode = (attributedString.string as NSString).substring(with: match.range(at: 1))
return emoji.shortcode == matchShortcode
}) else {
continue
@ -57,7 +62,7 @@ extension BaseEmojiLabel {
}
guard foundEmojis else {
completion(NSAttributedString(string: string))
completion(attributedString, false)
return
}
@ -65,10 +70,10 @@ extension BaseEmojiLabel {
// if e.g. the account changes before all emojis are loaded, don't bother trying to set them
guard let self = self, self.emojiIdentifier == identifier else { return }
let mutAttrString = NSMutableAttributedString(string: string)
let mutAttrString = NSMutableAttributedString(attributedString: attributedString)
// replaces the emojis starting from the end of the string as to not alter the indices of preceeding emojis
for match in matches.reversed() {
let shortcode = (string as NSString).substring(with: match.range(at: 1))
let shortcode = (attributedString.string as NSString).substring(with: match.range(at: 1))
guard let emojiImage = emojiImages[shortcode] else {
continue
}
@ -78,7 +83,11 @@ extension BaseEmojiLabel {
mutAttrString.replaceCharacters(in: match.range, with: attachmentStr)
}
completion(mutAttrString)
completion(mutAttrString, true)
}
}
func replaceEmojis(in string: String, emojis: [Emoji], identifier: String?, completion: @escaping (_ attributedString: NSAttributedString, _ didReplaceEmojis: Bool) -> Void) {
replaceEmojis(in: NSAttributedString(string: string), emojis: emojis, identifier: identifier, completion: completion)
}
}

View File

@ -13,7 +13,7 @@ import SafariServices
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
class ContentTextView: LinkTextView {
class ContentTextView: LinkTextView, BaseEmojiLabel {
weak var navigationDelegate: TuskerNavigationDelegate?
weak var overrideMastodonController: MastodonController?
@ -24,6 +24,11 @@ class ContentTextView: LinkTextView {
private(set) var hasEmojis = false
var emojiIdentifier: String?
var emojiRequests: [ImageCache.Request] = []
var emojiFont: UIFont { defaultFont }
var emojiTextColor: UIColor { defaultColor }
// The link range currently being previewed
private var currentPreviewedLinkRange: NSRange?
// The preview created in the previewForHighlighting method, so that we can use the same one in previewForDismissing.
@ -51,45 +56,11 @@ class ContentTextView: LinkTextView {
// MARK: - Emojis
func setEmojis(_ emojis: [Emoji]) {
guard !emojis.isEmpty else {
hasEmojis = false
return
}
hasEmojis = true
let emojiImages = MultiThreadDictionary<String, UIImage>(name: "ContentTextView Emoji Images")
let group = DispatchGroup()
for emoji in emojis {
group.enter()
_ = ImageCache.emojis.get(emoji.url) { (_, image) in
defer { group.leave() }
guard let image = image,
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: image) else {
return
}
emojiImages[emoji.shortcode] = transformedImage
replaceEmojis(in: attributedText!, emojis: emojis, identifier: emojiIdentifier) { attributedString, didReplaceEmojis in
guard didReplaceEmojis else {
return
}
}
group.notify(queue: .main) {
let mutAttrString = NSMutableAttributedString(attributedString: self.attributedText!)
let string = mutAttrString.string
let matches = emojiRegex.matches(in: string, options: [], range: mutAttrString.fullRange)
// replaces the emojis started from the end of the string as to not alter the indexes of the other emojis
for match in matches.reversed() {
let shortcode = (string as NSString).substring(with: match.range(at: 1))
guard let emojiImage = emojiImages[shortcode] else {
continue
}
let attachment = NSTextAttachment(emojiImage: emojiImage, in: self.font!, with: self.textColor ?? .label)
let attachmentStr = NSAttributedString(attachment: attachment)
mutAttrString.replaceCharacters(in: match.range, with: attachmentStr)
}
self.attributedText = mutAttrString
self.attributedText = attributedString
self.setNeedsLayout()
self.setNeedsDisplay()
}

View File

@ -26,7 +26,7 @@ class EmojiLabel: UILabel, BaseEmojiLabel {
emojiRequests = []
hasEmojis = true
replaceEmojis(in: attributedText.string, emojis: emojis, identifier: identifier) { [weak self] (newAttributedText) in
replaceEmojis(in: attributedText.string, emojis: emojis, identifier: identifier) { [weak self] (newAttributedText, _) in
guard let self = self, self.emojiIdentifier == identifier else { return }
self.attributedText = newAttributedText
self.setNeedsLayout()

View File

@ -37,7 +37,7 @@ class MultiSourceEmojiLabel: UILabel, BaseEmojiLabel {
recombine()
for (index, (string, emojis)) in pairs.enumerated() {
self.replaceEmojis(in: string, emojis: emojis, identifier: identifier) { (attributedString) in
self.replaceEmojis(in: string, emojis: emojis, identifier: identifier) { (attributedString, _) in
attributedStrings[index] = attributedString
DispatchQueue.main.async { [weak self] in
guard let self = self, self.emojiIdentifier == identifier else { return }

View File

@ -15,6 +15,7 @@ class StatusContentTextView: ContentTextView {
func setTextFrom(status: StatusMO) {
statusID = status.id
emojiIdentifier = status.id
setTextFromHtml(status.content)
setEmojis(status.emojis)
}