How to allow and get Memoji Stickers in a text field - ios

I'm trying to use Memoji Stickers on my app, but neither UITextField or TextField from SwiftUI displays them on the keyboard. How can I make them show up, and how do I retrieve them afterwards?
I tried different text input traits, but had no success. I even tried using the Twitter keyboard option, because the stickers work in the Twitter app, but it was to no avail.
Here's a picture of how it shows up for me in apps that support it, like WhatsApp, Telegram and Twitter

So... how you can get it is to implement the paste method on the view controller where your textfield/view is.
Then you can grab the image from the pasteboard. Basically
Objective-C
UIImage *image = [[UIPasteboard generalPasteboard] image];
or Swift
let image = UIPasteboard.general.image
Which will let you find the UIImage then you can wrap it in
Objective-c
NSAttributedString *memojiStrong = [[NSAttributedString alloc] initWithAttributedString: image];
Swift
let memojiString = NSAttributedString(attachment: image)
This will let you add it as a string to whatever you need to add it to.
You can find a little more for the image -> String if you want to save it for an image: https://www.hackingwithswift.com/example-code/system/how-to-insert-images-into-an-attributed-string-with-nstextattachment
and the poster for some credit to get me this far:
https://www.reddit.com/r/iOSProgramming/comments/cuz3ut/memoji_in_chat/ey4onqg/?context=3

set the "allowsEditingTextAttributes" property to YES in UITextField.

For RxSwift:
textView.rx.attributedText.subscribe(onNext: { [weak self] attributeString in
guard let attributeString = attributeString else { return }
attributeString.enumerateAttribute(NSAttributedStringKey.attachment, in: NSRange(location: 0, length: attributeString.length), options: [], using: {(value,range,_) -> Void in
if (value is NSTextAttachment) {
let attachment: NSTextAttachment? = (value as? NSTextAttachment)
var image: UIImage?
if ((attachment?.image) != nil) {
image = attachment?.image
} else {
image = attachment?.image(forBounds: (attachment?.bounds)!, textContainer: nil, characterIndex: range.location)
}
guard let pasteImage = image else { return }
guard let pngData = UIImagePNGRepresentation(pasteImage) else { return }
guard let pngImage = UIImage(data: pngData) else { return }
guard let attachmentImage = OutgoingFile(image: pngImage, type: .image, isPNG: true) else { return }
let newString = NSMutableAttributedString(attributedString: attributeString)
newString.replaceCharacters(in: range, with: "")
self?.newCommentBodyTextView.textView.attributedText = newString
self?.addFileOnNews(attachmentImage)
return
}
})
}).disposed(by: bag)
You can find more information in https://kostyakulakov.ru/how-to-get-memoji-from-uitextfield-or-uitextview-swift-4/

Related

Swift problem when adding annotations with regex to pdf page

I want to search for a regex in a pdf, and add annotations to it according, using the results from the regex. I have built a simple function that does this. As the amazing community (really amazing people who used their time helping me) posted I can I can use the decomposedStringWithCompatibilityMapping to search for the desired expression correctly in the pdf, but afterwards when I perform a pdf selection to find the bounds of it, I encounter a difference. I send you my code and some pictures.
func performRegex(regex:String, on pdfPage:PDFPage) {
guard let pdfString = pdfPage.string?.precomposedStringWithCanonicalMapping else { return }
guard let safeRegex = try? NSRegularExpression(pattern: regex, options: .caseInsensitive) else { return }
let results = safeRegex.matches(in: pdfString, options: .withoutAnchoringBounds, range: NSRange(pdfString.startIndex..., in: pdfString))
pdfPage.annotations.forEach { pdfPage.removeAnnotation($0)}
results.forEach { result in
let bbox = pdfPage.selection(for: result.range)?.bounds(for: pdfPage)
let annotation = PDFAnnotation(bounds: bbox!, forType: .highlight, withProperties: nil)
annotation.color = .yellow
annotation.contents = String(pdfString[Range(result.range, in:pdfString)!])
pdfPage.addAnnotation(annotation)
}
}
The problem is that when I do this and enter this expression [0-9] all my results are shifted:
While if I don't use precomposedStringWithCanonicalMapping, all my results are not shifted but I will encounter an error when I get a special character.
The problem (I suspect) is in this line of code.
let bbox = pdfPage.selection(for: result.range)?.bounds(for: pdfPage)
But I don't know any work arround for it.
Please if anyone can give me some help!
Thanks a lot
The only alternative I can think right now is to use the original string and fix the malformed ranges. Try like this:
var str = """
circular para poder realizar sus tareas laborales correspondientes a las actividades de comercialización de alimentos
"""
do {
let regex = try NSRegularExpression(pattern: ".", options: .caseInsensitive)
let results = regex.matches(in: str, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: str.utf16.count))
var badrange: NSRange?
results.forEach { result in
guard let range = Range(result.range, in: str) else {
if badrange != nil {
badrange!.length += 1
if let range = Range(badrange!, in: str) {
let newStr = str[range]
print(newStr)
}
} else {
badrange = result.range
}
return
}
let newStr = str[range]
print(newStr)
badrange = nil
}
} catch {
print(error)
}

Is there a way to store a pdf annotation on a click?

I'm building a Machine Learning app to read info of pdfs, to train my algorithm I need to get the pdf annotations location. Is there a way to set a gesture recognizer and add the annotation to an array upon click? I successfully added the annotations to a pdf upon a regex. But I need to add the annotation and its associated info (location in the document upon a click) can I add a gesture recognizer to an app? My app uses SwiftUI.
func makeNSView(context: NSViewRepresentableContext<PDFViewRepresentedView>) -> PDFViewRepresentedView.NSViewType {
let pdfView = PDFView()
let document = PDFDocument(url: url)
let regex = try! NSRegularExpression(pattern: #"[0-9.,]+(,|\.)\d\d"#, options: .caseInsensitive)
let string = document?.string!
let results = regex.matches(in: string!, options: .withoutAnchoringBounds, range: NSRange(0..<(string?.utf16.count)!))
let page = document?.page(at: 0)!
results.forEach { (result) in
let startIndex = result.range.location
let endIndex = result.range.location + result.range.length - 1
let selection = document?.selection(from: page!, atCharacterIndex: startIndex, to: page!, atCharacterIndex: endIndex)
print(selection!.bounds(for: page!))
let pdfAnnotation = PDFAnnotation(bounds: (selection?.bounds(for: page!))!, forType: .square, withProperties: nil)
document?.page(at: 0)?.addAnnotation(pdfAnnotation)
}
pdfView.document = document
return pdfView
}
and get the tap in
func annotationTapping(_ sender: NSClickGestureRecognizer){
print("------- annotationTapping ------")
}
If some one has achieved this, by adding an observer or something like this?
Thanks
The PDFView already has a tap gesture recognizer attached for annotations, so no need to add another one. When a tap occurs it will publish a PDFViewAnnotationHit notification. The annotation object can be found in the userInfo.
Set up an observer for the notification in makeUIView or wherever else makes sense.
NotificationCenter.default.addObserver(forName: .PDFViewAnnotationHit, object: nil, queue: nil) { (notification) in
if let annotation = notification.userInfo?["PDFAnnotationHit"] as? PDFAnnotation {
print(annotation.debugDescription)
}
}
or better, handle the notification in your SwiftUI View.
#State private var selectedAnnotation: PDFAnnotation?
var body: some View {
VStack {
Text("Selected Annotation Bounds: \(selectedAnnotation?.bounds.debugDescription ?? "none")")
SomeView()
.onReceive(NotificationCenter.default.publisher(for: .PDFViewAnnotationHit)) { (notification) in
if let annotation = notification.userInfo?["PDFAnnotationHit"] as? PDFAnnotation {
self.selectedAnnotation = annotation
}
}
}
}

UIWebView : Get Webview Content size?

I have loaded HTML in webview.  I want to dynamically update font size(increase or decrease) of text loaded in web view Same as in Apple News Article app. But According to my design I need to stop web view scrolling and need to update it height according to text. So I need to get content size of article, but I am not getting it proper. Can anyone help?
When font change at that time change Webview height to 1 than get proper offsetHeight for Webview
webView1.frame.size.height = 1
let size : String = self.webView1.stringByEvaluatingJavaScript(from: "document.documentElement.offsetHeight")!
add below lines of code into your webViewDidFinishLoad
webView.layoutSubviews()
webView.frame.size.height = 1
webView.frame.size = webView.sizeThatFits(.zero)
print("WebView Height : \(webView.scrollView.contentSize.height)")
webViewHgtConst.constant = webView.scrollView.contentSize.height
webView.scalesPageToFit = true
webView.scrollView.isScrollEnabled = false
webView.scrollView.maximumZoomScale = 1.0
webView.scrollView.minimumZoomScale = 1.0
and don't forget to add UIWebViewDelegate
and add YOUR_WEBVIEW.delegate = self to your viewDidLoad
Hope this will help you
First stop its scrolling :
webView.scrollView.isScrollEnabled = false
webView.scrollView.showsVerticalScrollIndicator = false
webView.scrollView.showsHorizontalScrollIndicator = false
webView.scrollView.bounces = false
webView.loadHTMLString(dataHtmlString, baseURL: nil)
Make outlet of webView height Constraint and give its value by calculating its height:
webviewHeightConst.constant = (dataHtmlString.htmlAttributedString()?.height(withConstrainedWidth: yourwidthcont))!
This Extension Calculates height
extension String {
func htmlAttributedString() -> NSAttributedString? {
guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
guard let html = try? NSMutableAttributedString(
data: data,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil) else { return nil }
var found = false
html.beginEditing()
html.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, html.length), options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (value, range, stop) in
if (value != nil) {
let oldFont = value as! UIFont
let newFont = oldFont.withSize(16)
html.addAttribute(NSFontAttributeName, value: newFont, range: range)
found = true
}
}
if !found{
// No font was found - do something else?
}
html.endEditing()
return html
}
}

Ruby text/ Furigana in ios

I am currently trying to display some text in Japanese on a UITextView. Is it possible to display the furigana above the kanji (like below) in a manner similar to the < rt> tag in html, without using a web view?
A lot of text processing is involved, therefore I cannot simply use a web view. In iOS8, CTRubyAnnotationRef was added but there is no documentation (I would welcome any example), and I am also concerned with the lack of compatibility with iOS7. I thought that it would be possible to display the furigana above with the use of an NSAttributedString, but couldn't as of yet.
Update Swift 5.1
This solution is an update of preview answers and let you write Asian sentences with Phonetic Guide, using a pattern in the strings.
Let's start from handling string.
these 4 extension let you to inject in a string the ruby annotation.
the function createRuby() check the string a pattern, that it is: |word written in kanji《phonetic guide》.
Examples:
|紅玉《ルビー》
|成功《せいこう》するかどうかは、きみの|努力《どりょく》に|係《かか》る。
and so on.
the important thing is to follow the pattern.
extension String {
// 文字列の範囲
private var stringRange: NSRange {
return NSMakeRange(0, self.utf16.count)
}
// 特定の正規表現を検索
private func searchRegex(of pattern: String) -> NSTextCheckingResult? {
do {
let patternToSearch = try NSRegularExpression(pattern: pattern)
return patternToSearch.firstMatch(in: self, range: stringRange)
} catch { return nil }
}
// 特定の正規表現を置換
private func replaceRegex(of pattern: String, with templete: String) -> String {
do {
let patternToReplace = try NSRegularExpression(pattern: pattern)
return patternToReplace.stringByReplacingMatches(in: self, range: stringRange, withTemplate: templete)
} catch { return self }
}
// ルビを生成
func createRuby() -> NSMutableAttributedString {
let textWithRuby = self
// ルビ付文字(「|紅玉《ルビー》」)を特定し文字列を分割
.replaceRegex(of: "(|.+?《.+?》)", with: ",$1,")
.components(separatedBy: ",")
// ルビ付文字のルビを設定
.map { component -> NSAttributedString in
// ベース文字(漢字など)とルビをそれぞれ取得
guard let pair = component.searchRegex(of: "|(.+?)《(.+?)》") else {
return NSAttributedString(string: component)
}
let component = component as NSString
let baseText = component.substring(with: pair.range(at: 1))
let rubyText = component.substring(with: pair.range(at: 2))
// ルビの表示に関する設定
let rubyAttribute: [CFString: Any] = [
kCTRubyAnnotationSizeFactorAttributeName: 0.5,
kCTForegroundColorAttributeName: UIColor.darkGray
]
let rubyAnnotation = CTRubyAnnotationCreateWithAttributes(
.auto, .auto, .before, rubyText as CFString, rubyAttribute as CFDictionary
)
return NSAttributedString(string: baseText, attributes: [kCTRubyAnnotationAttributeName as NSAttributedString.Key: rubyAnnotation])
}
// 分割されていた文字列を結合
.reduce(NSMutableAttributedString()) { $0.append($1); return $0 }
return textWithRuby
}
}
Ruby Label: the big problem
As you maybe know, Apple has introduced in iOS 8 the ruby annotation like attribute for the attributedString, and if you did create the the attributed string with ruby annotation and did:
myLabel.attributedText = attributedTextWithRuby
the label did shows perfectly the string without problem.
From iOS 11, Apple unfortunately has removed this feature and, so, if want to show ruby annotation you have override the method draw, to effectively draw the text. To do this, you have to use Core Text to handle the text hand it's lines.
Let's show the code
import UIKit
public enum TextOrientation { //1
case horizontal
case vertical
}
class RubyLabel: UILabel {
public var orientation:TextOrientation = .horizontal //2
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
// ルビを表示
override func draw(_ rect: CGRect) {
//super.draw(rect) //3
// context allows you to manipulate the drawing context (i'm setup to draw or bail out)
guard let context: CGContext = UIGraphicsGetCurrentContext() else {
return
}
guard let string = self.text else { return }
let attributed = NSMutableAttributedString(attributedString: string.createRuby()) //4
let path = CGMutablePath()
switch orientation { //5
case .horizontal:
context.textMatrix = CGAffineTransform.identity;
context.translateBy(x: 0, y: self.bounds.size.height);
context.scaleBy(x: 1.0, y: -1.0);
path.addRect(self.bounds)
attributed.addAttribute(NSAttributedString.Key.verticalGlyphForm, value: false, range: NSMakeRange(0, attributed.length))
case .vertical:
context.rotate(by: .pi / 2)
context.scaleBy(x: 1.0, y: -1.0)
//context.saveGState()
//self.transform = CGAffineTransform(rotationAngle: .pi/2)
path.addRect(CGRect(x: self.bounds.origin.y, y: self.bounds.origin.x, width: self.bounds.height, height: self.bounds.width))
attributed.addAttribute(NSAttributedString.Key.verticalGlyphForm, value: true, range: NSMakeRange(0, attributed.length))
}
attributed.addAttributes([NSAttributedString.Key.font : self.font], range: NSMakeRange(0, attributed.length))
let frameSetter = CTFramesetterCreateWithAttributedString(attributed)
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0,attributed.length), path, nil)
// Check need for truncate tail
//6
if (CTFrameGetVisibleStringRange(frame).length as Int) < attributed.length {
// Required truncate
let linesNS: NSArray = CTFrameGetLines(frame)
let linesAO: [AnyObject] = linesNS as [AnyObject]
var lines: [CTLine] = linesAO as! [CTLine]
let boundingBoxOfPath = path.boundingBoxOfPath
let lastCTLine = lines.removeLast() //7
let truncateString:CFAttributedString = CFAttributedStringCreate(nil, "\u{2026}" as CFString, CTFrameGetFrameAttributes(frame))
let truncateToken:CTLine = CTLineCreateWithAttributedString(truncateString)
let lineWidth = CTLineGetTypographicBounds(lastCTLine, nil, nil, nil)
let tokenWidth = CTLineGetTypographicBounds(truncateToken, nil, nil, nil)
let widthTruncationBegins = lineWidth - tokenWidth
if let truncatedLine = CTLineCreateTruncatedLine(lastCTLine, widthTruncationBegins, .end, truncateToken) {
lines.append(truncatedLine)
}
var lineOrigins = Array<CGPoint>(repeating: CGPoint.zero, count: lines.count)
CTFrameGetLineOrigins(frame, CFRange(location: 0, length: lines.count), &lineOrigins)
for (index, line) in lines.enumerated() {
context.textPosition = CGPoint(x: lineOrigins[index].x + boundingBoxOfPath.origin.x, y:lineOrigins[index].y + boundingBoxOfPath.origin.y)
CTLineDraw(line, context)
}
}
else {
// Not required truncate
CTFrameDraw(frame, context)
}
}
//8
override var intrinsicContentSize: CGSize {
let baseSize = super.intrinsicContentSize
return CGSize(width: baseSize.width, height: baseSize.height * 1.0)
}
}
Code explanation:
1- Chinese and japanese text can be written in horizontal and vertical way. This enumeration let you switch in easy way between horizontal and vertical orietantation.
2- public variable with switch orientation text.
3- this method must be commented. the reason is that call it you see two overlapping strings:one without attributes, last your attributed string.
4- here call the method of String class extension in which you create the attributed string with ruby annotation.
5- This switch, rotate if need the context in which draw your text in case you want show vertical text. In fact in this switch you add the attribute NSAttributedString.Key.verticalGlyphForm that in case vertical is true, false otherwise.
6- This 'if' is particular important because, the label, cause we had commented the method 'super.draw()' doesn't know how to manage a long string. without this 'if', the label thinks to have only one line to draw. And so, you still to have a string with '...' like tail. In this 'if' the string is broken in more line and drawing correctly.
7- When you don't give to label some settings, the label knows to have more one line but because it can't calculate what is the showable last piece of string, give error in execution time and the app goes in crash. So be careful. But, don't worry! we talk about the right settings to give it later.
8- this is very important to fit the label to text's size.
How to use the RubyLabel
the use of the label is very simple:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var rubyLabel: RubyLabel! //1
override func viewDidLoad() {
super.viewDidLoad()
setUpLabel()
}
private func setUpLabel() {
rubyLabel.text = "|成功《せいこう》するかどうかは、きみの|努力《どりょく》に|係《かか》る。|人々《ひとびと》の|生死《せいし》に|係《かか》る。" //2
//3
rubyLabel.textAlignment = .left
rubyLabel.font = .systemFont(ofSize: 20.0)
rubyLabel.orientation = .horizontal
rubyLabel.lineBreakMode = .byCharWrapping
}
}
Code Explanation:
1- connect label to xib if use storyboard or xib file or create label.
2- as I say, the use is very simple: here assign the string with ruby pattern like any other string
3- these setting are the setting to have to set to make work the label. You can set via code or via storyboard/xib
Be careful
if you use storyboard/xib, if you don't put correctly the constraints, the label give you the error at point n° 7.
Result
Works, but not perfect
As you can see by screenshot, this label work well but still has some problem.
1- with vertical text the label still in horizontal shape;
2- if the string contains \n to split the string in more lines, the label shows only the number of lines that the string would have had if was without the '\n' character.
I'm working for fix these problem, but your help is appreciated.
In the end I created a function that gets the kanji's position and create labels every time, above the kanji. It's quite dirty but it's the only way to insure a compatibility with iOS7.
However I have to mention the remarkable examples that you can find on GitHub for the CTRubyAnnotationRef nowadays like : http://dev.classmethod.jp/references/ios8-ctrubyannotationref/
and
https://github.com/shinjukunian/SimpleFurigana
Good luck to all of you !
Here's a focused answer with some comments. This works for UITextView and UILabel on iOS 16.
let rubyAttributes: [CFString : Any] = [
kCTRubyAnnotationSizeFactorAttributeName : 0.5,
kCTRubyAnnotationScaleToFitAttributeName : 0.5,
]
let annotation = CTRubyAnnotationCreateWithAttributes(
.center, // Alignment relative to base text
.auto, // Overhang for adjacent characters
.before, // `before` = above, `after` = below, `inline` = after the base text (for horizontal text)
"Ruby!" as CFString,
rubyAttributes as CFDictionary
)
let stringAttributes = [kCTRubyAnnotationAttributeName as NSAttributedString.Key : annotation]
NSAttributedString(string: "Base Text!", attributes: stringAttributes)
Note, you may want to UITextView.textContainerInset.top to something larger than the default to avoid having the ruby clipped by the scrollview.

how to resize an image or done as a NSAttributedString NSTextAttachment (or set its initital size)

I have a NSAttributedString to which I am adding a NSTextAttachment. The image is 50w by 50h but I'd like it to scale down to reflect the line height of the attributed string. I thought this would be done automatically but I guess not. I have looked at the UImage class reference but this image doesn't seem to be set in a UIImageView so no access to a frame property. Here's a screenshot of what I currently have:
In an ideal world, I would also like to implement a way to scale up the image based upon user input (such as increasing the font size). Any ideas on how to achieve this?
thx
edit 1
here's how I'm creating it:
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:#"note-small.png"];
NSLog(#"here is the scale: %f", textAttachment.image.scale);
NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];
[headerAS replaceCharactersInRange:NSMakeRange([headerAS length], 0) withString:#" "];
[headerAS replaceCharactersInRange:NSMakeRange([headerAS length], 0) withAttributedString:attrStringWithImage];
You should set bounds form attachment to resize image like this:
attachment.bounds = CGRectMake(0, 0, yourImgWidth, yourImgHeight)
If you need to resize a bunch of NSTextAttachment images while keeping their aspect ratio i've written a handy extension: http://hack.swic.name/convenient-nstextattachment-image-resizing
extension NSTextAttachment {
func setImageHeight(height: CGFloat) {
guard let image = image else { return }
let ratio = image.size.width / image.size.height
bounds = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: ratio * height, height: height)
}
}
Example usage:
let textAttachment = NSTextAttachment()
textAttachment.image = UIImage(named: "Image")
textAttachment.setImageHeight(16) // Whatever you need to match your font
let imageString = NSAttributedString(attachment: textAttachment)
yourAttributedString.appendAttributedString(imageString)
Look at subclassing NSTextAttachment and implementing the NSTextAttachmentContainer methods to return different sizes based on the text container supplied. By default, NSTextAttachment just returns the size of the image it is provided with.
Swift4
if you want a attributed string along with the image or a icon
here you can do something like this.
func AttributedTextwithImgaeSuffixAndPrefix(AttributeImage1 : UIImage , AttributedText : String ,AttributeImage2 : UIImage, LabelBound : UILabel) -> NSMutableAttributedString
{
let fullString = NSMutableAttributedString(string: " ")
let image1Attachment = NSTextAttachment()
image1Attachment.bounds = CGRect(x: 0, y: ((LabelBound.font.capHeight) - AttributeImage1.size.height).rounded() / 2, width: AttributeImage1.size.width, height: AttributeImage1.size.height)
image1Attachment.image = AttributeImage1
let image1String = NSAttributedString(attachment: image1Attachment)
let image2Attachment = NSTextAttachment()
image2Attachment.bounds = CGRect(x: 0, y: ((LabelBound.font.capHeight) - AttributeImage2.size.height).rounded() / 2, width: AttributeImage2.size.width, height: AttributeImage2.size.height)
image2Attachment.image = AttributeImage2
let image2String = NSAttributedString(attachment: image2Attachment)
fullString.append(image1String)
fullString.append(NSAttributedString(string: AttributedText))
fullString.append(image2String)
return fullString
}
you can use this code as mentioned below:
self.lblMid.attributedText = AttributedTextwithImgaeSuffixAndPrefix(AttributeImage1: #imageLiteral(resourceName: "Left") , AttributedText: " Payment Details ", AttributeImage2: #imageLiteral(resourceName: "RIght") , LabelBound: self.lblMid)
here you can add images you can replace it with your own:
Left Image
Right Image
Out put will be like this
NSTextAtatchment is just a holder for a UIImage so scale the image when it needs scaling and recreate the text attachment or set it's image. You'll need to force a nslayout update if you change the image on an existing text attachment.
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"
}

Resources