diff --git a/Tusker/Views/BaseEmojiLabel.swift b/Tusker/Views/BaseEmojiLabel.swift index a4e66147..c6a4c9f4 100644 --- a/Tusker/Views/BaseEmojiLabel.swift +++ b/Tusker/Views/BaseEmojiLabel.swift @@ -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) + } } diff --git a/Tusker/Views/ContentTextView.swift b/Tusker/Views/ContentTextView.swift index 5f86f999..12195d63 100644 --- a/Tusker/Views/ContentTextView.swift +++ b/Tusker/Views/ContentTextView.swift @@ -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(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() } diff --git a/Tusker/Views/EmojiLabel.swift b/Tusker/Views/EmojiLabel.swift index f7e7e690..25f87264 100644 --- a/Tusker/Views/EmojiLabel.swift +++ b/Tusker/Views/EmojiLabel.swift @@ -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() diff --git a/Tusker/Views/MultiSourceEmojiLabel.swift b/Tusker/Views/MultiSourceEmojiLabel.swift index 75fc5c7f..c6229ffe 100644 --- a/Tusker/Views/MultiSourceEmojiLabel.swift +++ b/Tusker/Views/MultiSourceEmojiLabel.swift @@ -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 } diff --git a/Tusker/Views/StatusContentTextView.swift b/Tusker/Views/StatusContentTextView.swift index be565d70..6ebf00ac 100644 --- a/Tusker/Views/StatusContentTextView.swift +++ b/Tusker/Views/StatusContentTextView.swift @@ -15,6 +15,7 @@ class StatusContentTextView: ContentTextView { func setTextFrom(status: StatusMO) { statusID = status.id + emojiIdentifier = status.id setTextFromHtml(status.content) setEmojis(status.emojis) }