// // BaseEmojiLabel.swift // Tusker // // Created by Shadowfacts on 10/18/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import Pachyderm private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) protocol BaseEmojiLabel: AnyObject { var emojiIdentifier: String? { get set } var emojiRequests: [ImageCache.Request] { get set } var emojiFont: UIFont { get } var emojiTextColor: UIColor { get } } extension BaseEmojiLabel { 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(attributedString, false) return } let emojiImages = MultiThreadDictionary(name: "BaseEmojiLabel Emoji Images") var foundEmojis = false let group = DispatchGroup() 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) -> Bool in let matchShortcode = (attributedString.string as NSString).substring(with: match.range(at: 1)) return emoji.shortcode == matchShortcode }) else { continue } foundEmojis = true group.enter() let request = 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 } if let request = request { emojiRequests.append(request) } } guard foundEmojis else { completion(attributedString, false) return } group.notify(queue: .main) { [weak self] in // 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(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 = (attributedString.string as NSString).substring(with: match.range(at: 1)) guard let emojiImage = emojiImages[shortcode] else { continue } let attachment = NSTextAttachment(emojiImage: emojiImage, in: self.emojiFont, with: self.emojiTextColor) let attachmentStr = NSAttributedString(attachment: attachment) mutAttrString.replaceCharacters(in: match.range, with: attachmentStr) } 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) } }