I am trying to replace an image in an NSAttributedString
with a scaled copy of itself (to fit a UITextView
's width), however, in many (not all) cases, the replaced image is a mask of the original. I assume that there is something in the other attributes of the string which is doing this as both the image extraction and the creation of a new attributed string from the image work perfectly.
Note: answers like this how to resize an image or done as a NSAttributedString NSTextAttachment (or set its initital size) show how to construct a string with images (which I can do) or how to modify the size of a text attachment (again, I can do that) prior to creating the string, but not how to modify in place.
Update
Thanks to suggestions by commentator @Larme I have now solved my original problem of resizing an image in-place by changing the original NSTextAttachment.bounds
- note, not replacing it, changing it in situ. I'm not going to post that as an answer to my own question, as it doesn't answer the wider question of replacing an image.
Any tips as to how to proceed gratefully accepted.
Here's the basis:
extension NSAttributedString {
func resizeAttachments(maxWidth: CGFloat) -> NSAttributedString {
var replacement = NSMutableAttributedString(attributedString: self)
func replace(_ image: UIImage, in range: NSRange, dict: [NSAttributedString.Key : Any]) {
// ... see below for variants of this
}
self.enumerateAttributes(in: NSMakeRange(0, self.length),
options: [],
using: {dict, range, stop in
for (key, value) in dict {
if key == .attachment, let attachment = value as? NSTextAttachment {
if
let fw = attachment.fileWrapper, fw.isRegularFile,
let d = fw.regularFileContents,
let image = UIImage(data: d)?.resized(toWidth: maxWidth) {
// removing the resize above makes no difference to the issue
// i.e. replacing the image with itself still causes the same problem
replace(image, in: range, dict: dict)
return // Exit the block. Can't have 2 .attachments.
}
}
}
})
return replacement
}
}
So far, a straight forward enumeration on self
, modifying the mutable copy string replacement
when an image is encountered. Here's what I get:
From this unscaled original:
So, what have I tried in the replace function?
func replace(_ image: UIImage, in range: NSRange, dict: [NSAttributedString.Key : Any]) {
let newAttachment = NSTextAttachment(image: image)
let newCharacter = NSMutableAttributedString(attachment: newAttachment)
// NB if I replace the entire string with `newCharacter` the image is perfect (but obviously the rest of the string has gone)
// Option 1
replacement.replaceCharacters(in: range, with: newCharacter)
// Option 2
var newDict: [NSAttributedString.Key : Any] = dict
newDict[.attachment] = newAttachment
replacement.addAttributes(newDict, range: range)
// Option 3
replacement.removeAttribute(.attachment, range: range)
replacement.addAttribute(.attachment, value: newAttachment, range: range)
}
I can't find anything anywhere about actually replacing images. As I said, the extraction of the image works fine - if I just return a new attributed string with only the extracted image then it's perfect. If I append the image to the end, it works. It's modifying it in place that seems to be the issue. Has anyone ever done this successfully?