Reputation: 441
Problem:
I need a contentMode .scaleAspectFit like property for a image used as a NSTextAttachment.
My attachment image does not display well in a pre-set displaySize.
Replicate:
I set the displaySize like this, but I need to make the image aspect fit as the attachment.
attachment = RemoteImageTextAttachment(imageURL: fileUrl!, displaySize: CGSize(width: 300, height: 300))
I need to replicate the following function, but modify it to aspect fit the attachment image to fit all height and width images within the set displaySize.
public class RemoteImageTextAttachment: NSTextAttachment {
// The label that this attachment is being added to
public weak var label: UILabel?
// The size to display the image. If nil, the image's size will be used
public var displaySize: CGSize?
public var downloadQueue: DispatchQueue?
public let imageUrl: URL
private weak var textContainer: NSTextContainer?
private var isDownloading = false
var imageCache = AutoPurgingImageCache()
public init(imageURL: URL, displaySize: CGSize? = nil, downloadQueue: DispatchQueue? = nil) {
self.imageUrl = imageURL
self.displaySize = displaySize
self.downloadQueue = downloadQueue
super.init(data: nil, ofType: nil)
}
required init?(coder: NSCoder) {
fatalError()
}
override public func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
if let displaySize = displaySize {
return CGRect(origin: .zero, size: displaySize)
}
if let originalImageSize = image?.size {
return CGRect(origin: .zero, size: originalImageSize)
}
// If we return .zero, the image(forBounds:textContainer:characterIndex:) function won't be called
let placeholderSize = CGSize(width: 1, height: 1)
return CGRect(origin: .zero, size: placeholderSize)
}
override public func image(forBounds imageBounds: CGRect, textContainer: NSTextContainer?, characterIndex charIndex: Int) -> UIImage? {
if let image = image {
return image
}
self.textContainer = textContainer
guard !isDownloading else {
return nil
}
isDownloading = true
let imageUrl = self.imageUrl
let downloadQueue = self.downloadQueue ?? DispatchQueue.global()
downloadQueue.async { [weak self] in
let data = try? Data(contentsOf: imageUrl)
DispatchQueue.main.async { [weak textContainer] in
guard let strongSelf = self else {
return
}
defer {
strongSelf.isDownloading = false
}
guard let data = data else {
return
}
strongSelf.image = UIImage(data: data)
strongSelf.label?.setNeedsDisplay()
// For UITextView/NSTextView
if let layoutManager = self?.textContainer?.layoutManager,
let ranges = layoutManager.rangesForAttachment(strongSelf) {
ranges.forEach { range in
layoutManager.invalidateLayout(forCharacterRange: range, actualCharacterRange: nil)
}
}
}
}
return nil
}
}
public extension NSLayoutManager {
func rangesForAttachment(_ attachment: NSTextAttachment) -> [NSRange]? {
guard let textStorage = textStorage else {
return nil
}
var ranges: [NSRange] = []
textStorage.enumerateAttribute(.attachment, in: NSRange(location: 0, length: textStorage.length), options: [], using: { (attribute, range, _) in
if let foundAttribute = attribute as? NSTextAttachment, foundAttribute === attachment {
ranges.append(range)
}
})
return ranges
}
}
Pseudocode:
if let displaySize = displaySize {
displaySize.contentMode = .scaleAspectFit
return CGRect(origin: .zero, size: displaySize)
}
Upvotes: 0
Views: 135
Reputation: 441
I found a way to take the original image size and resize that original image to fit any target CGSize or container.
if let originalImageSize = image?.size {
let targetSize = CGSize(width: 300, height: 300)
// Compute the scaling ratio for the width and height separately
let widthScaleRatio = targetSize.width / originalImageSize.width
let heightScaleRatio = targetSize.height / originalImageSize.height
// To keep the aspect ratio, scale by the smaller scaling ratio
let scaleFactor = min(widthScaleRatio, heightScaleRatio)
// Multiply the original image’s dimensions by the scale factor
// to determine the scaled image size that preserves aspect ratio
let scaledImageSize = CGSize(
width: originalImageSize.width * scaleFactor,
height: originalImageSize.height * scaleFactor)
return CGRect(origin: .zero, size: scaledImageSize)
}
Source - Resize UIImage Without Stretching in Swift
Upvotes: 0