Thanks @Dung Nguyen, for his answer. I found that his solution works well on iOS devices, but not working on macOS when trying to update an attachment in a large NSAttributedString. So I searched for my own solution for that. Here it is
let newSize: NSSize = \\ the new size for the image attachment
let originalAttachment: NSTextAttachment = \\ wherever the attachment comes from
let range: NSRange = \\ the range of the original attachment in a wholeAttributedString object which is an NSMutableAttributedString
if originalAttachment.fileWrapper != nil,
originalAttachment.fileWrapper!.isRegularFile {
if let contents = originalAttachment.fileWrapper!.regularFileContents {
let newAttachment = NSTextAttachment()
newAttachment.image = NSImage(data: contents)
#if os(iOS)
newAttachment.fileType = originalAttachment.fileType
newAttachment.contents = originalAttachment.contents
newAttachment.fileWrapper = originalAttachment.fileWrapper
#endif
newAttachment.bounds = CGRect(x: originalAttachment.bounds.origin.x,
y: originalAttachment.bounds.origin.y,
width: newSize.width, height: newSize.height)
let newAttachmentString = NSAttributedString(attachment: newAttachment)
wholeAttributedString.replaceCharacters(in: range, with: newAttachmentString)
}
}
Also, on different OS, the image size or bounds an image attachment returns can be different. In order to extract the correct image size, I wrote the following extensions:
extension FileWrapper {
func imageSize() -> CPSize? {
if self.isRegularFile {
if let contents = self.regularFileContents {
if let image: CPImage = CPImage(data: contents) {
#if os(iOS)
return image.size
#elseif os(OSX)
if let rep = image.representations.first {
return NSMakeSize(CGFloat(rep.pixelsWide),
CGFloat(rep.pixelsHigh))
}
#endif
}
}
}
return nil
}
}
extension NSTextAttachment {
func imageSize() -> CPSize? {
var imageSize: CPSize?
if self.fileType != nil &&
AttachmentFileType(rawValue: self.fileType!) != nil {
if self.bounds.size.width > 0 {
imageSize = self.bounds.size
} else if self.image?.size != nil {
imageSize = self.image!.size
} else if let fileWrapper = self.fileWrapper {
if let imageSizeFromFileWrapper = fileWrapper.imageSize() {
imageSize = imageSizeFromFileWrapper
}
}
}
return imageSize
}
}
#if os(iOS)
import UIKit
public typealias CPSize = CGSize
#elseif os(OSX)
import Cocoa
public typealias CPSize = NSSize
#endif
#if os(iOS)
import UIKit
public typealias CPImage = UIImage
#elseif os(OSX)
import AppKit
public typealias CPImage = NSImage
#endif
enum AttachmentFileType: String {
case jpeg = "public.jpeg"
case jpg = "public.jpg"
case png = "public.png"
case gif = "public.gif"
}