From f4e9f26df487939291169b51835226347a7f2fe7 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Mon, 22 Feb 2021 18:51:31 -0800 Subject: [PATCH] Fix animated text attachment positioning --- ...NSMutableAttributedString+Extensions.swift | 14 ++++++-- Views/UIKit/AnimatedAttachmentLabel.swift | 34 +++++++------------ Views/UIKit/AnimatedTextAttachment.swift | 9 ++--- Views/UIKit/AnimatingLayoutManager.swift | 31 ++++++----------- 4 files changed, 41 insertions(+), 47 deletions(-) diff --git a/Extensions/NSMutableAttributedString+Extensions.swift b/Extensions/NSMutableAttributedString+Extensions.swift index 3fd057b..2170849 100644 --- a/Extensions/NSMutableAttributedString+Extensions.swift +++ b/Extensions/NSMutableAttributedString+Extensions.swift @@ -1,6 +1,7 @@ // Copyright © 2020 Metabolist. All rights reserved. import Mastodon +import SDWebImage import UIKit import ViewModels @@ -11,12 +12,21 @@ extension NSMutableAttributedString { while let tokenRange = string.range(of: token) { let attachment = AnimatedTextAttachment() + let imageURL: URL if !identityContext.appPreferences.shouldReduceMotion, identityContext.appPreferences.animateCustomEmojis { - attachment.imageURL = emoji.url + imageURL = emoji.url } else { - attachment.imageURL = emoji.staticUrl + imageURL = emoji.staticUrl + } + + attachment.imageView.sd_setImage(with: imageURL) { image, _, _, _ in + attachment.image = image + + DispatchQueue.main.async { + view.setNeedsDisplay() + } } attachment.accessibilityLabel = emoji.shortcode diff --git a/Views/UIKit/AnimatedAttachmentLabel.swift b/Views/UIKit/AnimatedAttachmentLabel.swift index 0857bd3..91402d9 100644 --- a/Views/UIKit/AnimatedAttachmentLabel.swift +++ b/Views/UIKit/AnimatedAttachmentLabel.swift @@ -22,10 +22,20 @@ final class AnimatedAttachmentLabel: UILabel, EmojiInsertable { attributedText.enumerateAttribute( .attachment, - in: NSRange(location: 0, length: attributedText.length)) { attachment, _, _ in - guard let attachmentImageView = (attachment as? AnimatedTextAttachment)?.imageView else { return } + in: NSRange(location: 0, length: attributedText.length), + options: .longestEffectiveRangeNotRequired) { attachment, _, _ in + guard let animatedAttachment = attachment as? AnimatedTextAttachment, + let imageBounds = animatedAttachment.imageBounds + else { return } - attachmentImageViews.insert(attachmentImageView) + animatedAttachment.imageView.frame = imageBounds + animatedAttachment.imageView.contentMode = .scaleAspectFit + + if animatedAttachment.imageView.superview != self { + addSubview(animatedAttachment.imageView) + } + + attachmentImageViews.insert(animatedAttachment.imageView) } for subview in subviews { @@ -35,23 +45,5 @@ final class AnimatedAttachmentLabel: UILabel, EmojiInsertable { attachmentImageView.removeFromSuperview() } } - - attributedText.enumerateAttribute( - .attachment, - in: NSRange(location: 0, length: attributedText.length), - options: .longestEffectiveRangeNotRequired) { attachment, _, _ in - guard let animatedAttachment = attachment as? AnimatedTextAttachment, - let imageBounds = animatedAttachment.imageBounds - else { return } - - animatedAttachment.imageView.frame = imageBounds - animatedAttachment.imageView.image = animatedAttachment.image - animatedAttachment.imageView.contentMode = .scaleAspectFit - animatedAttachment.imageView.sd_setImage(with: animatedAttachment.imageURL) - - if animatedAttachment.imageView.superview != self { - addSubview(animatedAttachment.imageView) - } - } } } diff --git a/Views/UIKit/AnimatedTextAttachment.swift b/Views/UIKit/AnimatedTextAttachment.swift index 7e6841e..7917644 100644 --- a/Views/UIKit/AnimatedTextAttachment.swift +++ b/Views/UIKit/AnimatedTextAttachment.swift @@ -4,20 +4,21 @@ import SDWebImage import UIKit final class AnimatedTextAttachment: NSTextAttachment { - var imageURL: URL? - var imageView = SDAnimatedImageView() - var imageBounds: CGRect? + let imageView = SDAnimatedImageView() + private(set) var imageBounds: CGRect? override func image(forBounds imageBounds: CGRect, textContainer: NSTextContainer?, characterIndex charIndex: Int) -> UIImage? { if let textContainer = textContainer, + let layoutManager = textContainer.layoutManager, let textContainerImageBounds = textContainer.layoutManager?.boundingRect( - forGlyphRange: NSRange(location: charIndex, length: 1), + forGlyphRange: NSRange(location: layoutManager.glyphIndexForCharacter(at: charIndex), length: 1), in: textContainer), textContainerImageBounds != .zero { self.imageBounds = textContainerImageBounds } else { + // Labels sometimes, but not always, end up in this path self.imageBounds = imageBounds } diff --git a/Views/UIKit/AnimatingLayoutManager.swift b/Views/UIKit/AnimatingLayoutManager.swift index 866044e..d06985c 100644 --- a/Views/UIKit/AnimatingLayoutManager.swift +++ b/Views/UIKit/AnimatingLayoutManager.swift @@ -18,9 +18,18 @@ final class AnimatingLayoutManager: NSLayoutManager { textStorage.enumerateAttribute( .attachment, in: NSRange(location: 0, length: textStorage.length)) { attachment, _, _ in - guard let attachmentImageView = (attachment as? AnimatedTextAttachment)?.imageView else { return } + guard let animatedAttachment = attachment as? AnimatedTextAttachment, + let imageBounds = animatedAttachment.imageBounds + else { return } - attachmentImageViews.insert(attachmentImageView) + animatedAttachment.imageView.frame = imageBounds + animatedAttachment.imageView.contentMode = .scaleAspectFit + + if animatedAttachment.imageView.superview != view { + view?.addSubview(animatedAttachment.imageView) + } + + attachmentImageViews.insert(animatedAttachment.imageView) } for subview in view?.subviews ?? [] { @@ -31,24 +40,6 @@ final class AnimatingLayoutManager: NSLayoutManager { } } - textStorage.enumerateAttribute( - .attachment, - in: glyphsToShow, - options: .longestEffectiveRangeNotRequired) { attachment, range, _ in - guard let animatedAttachment = attachment as? AnimatedTextAttachment, - let textContainer = textContainer(forGlyphAt: range.location, effectiveRange: nil) - else { return } - - animatedAttachment.imageView.frame = boundingRect(forGlyphRange: range, in: textContainer) - animatedAttachment.imageView.image = animatedAttachment.image - animatedAttachment.imageView.contentMode = .scaleAspectFit - animatedAttachment.imageView.sd_setImage(with: animatedAttachment.imageURL) - - if animatedAttachment.imageView.superview != view { - view?.addSubview(animatedAttachment.imageView) - } - } - super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin) } }