NSTextAttachment() Extra Properties - ios

I am wanting to add extra info onto some of the images I add to my UITextView. The class is NSTextAttachment(). I need my images to have some String descriptions so the app knows what's inside the image. How do I add this extra functionality? Is it through an Extension?
let attachment = NSTextAttachment()
attachment.image = image
attachment.bounds = CGRect(origin: .zero, size: image.size)
// Extra properties wanted
attachment.description = "add in info about what is in attachment"
attachment.moreInfo = "some more info about the attachment"

class EmojiTextAttachment:NSTextAttachment{
}
private var EmotagAssociationKey:String? = nil
extension EmojiTextAttachment{
var emoticonName:String?{
get {return objc_getAssociatedObject(self,&EmotagAssociationKey) as? String ?? ""}
set {objc_setAssociatedObject(self, &EmotagAssociationKey, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
It's been a while, but my solution is up.
Extends NSTextAttachment
You can add parameters with objc_getAssociatedObject and objc_setAssociatedObject
When using it, use EmojiTextAttachment instead of NSTextAttachment.
let emoticon:EmojiTextAttachment = EmojiTextAttachment()
let image = UIImage(named: myImage) as UIImage!
emoticon.image = image?.resizeSelf(newWidth, newHeight)
emoticon.emoticonName = emoname
The docs of objc_getAssociatedObject https://developer.apple.com/documentation/objectivec/1418865-objc_getassociatedobject

Related

How to combine a Gif Image into UIImageView with overlaying UIImageView in swift?

A gif image is loaded into a UIImageView (by using this extension) and another UIImageView is overlaid on it. Everything works fine but the problem is when I going for combine both via below code, it shows a still image (.jpg). I wanna combine both and after combine it should be a animated image (.gif) too.
let bottomImage = gifPlayer.image
let topImage = UIImage
let size = CGSize(width: (bottomImage?.size.width)!, height: (bottomImage?.size.height)!)
UIGraphicsBeginImageContext(size)
let areaSize = CGRect(x: 0, y: 0, width: size.width, height: size.height)
bottomImage!.draw(in: areaSize)
topImage!.draw(in: areaSize, blendMode: .normal, alpha: 0.8)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Click here to know more about this problem please.
When using an animated GIF in a UIImageView, it becomes an array of UIImage.
We can set that array with (for example):
imageView.animationImages = arrayOfImages
imageView.animationDuration = 1.0
or, we can set the .image property to an animatedImage -- that's how the GIF-Swift code you are using works:
if let img = UIImage.gifImageWithName("funny") {
bottomImageView.image = img
}
in that case, the image also contains the duration:
img.images?.duration
So, to generate a new animated GIF with the border/overlay image, you need to get that array of images and generate each "frame" with the border added to it.
Here's a quick example...
This assumes:
you are using GIF-Swift
you have added bottomImageView and topImageView in Storyboard
you have a GIF in the bundle named "funny.gif" (edit the code if yours is different)
you have a "border.png" in assets (again, edit the code as needed)
and you have a button to connect to the #IBAction:
import UIKit
import ImageIO
import UniformTypeIdentifiers
class animImageViewController: UIViewController {
#IBOutlet var bottomImageView: UIImageView!
#IBOutlet var topImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
if let img = UIImage.gifImageWithName("funny") {
bottomImageView.image = img
}
if let img = UIImage(named: "border") {
topImageView.image = img
}
}
#IBAction func saveButtonTapped(_ sender: Any) {
generateNewGif(from: bottomImageView, with: topImageView)
}
func generateNewGif(from animatedImageView: UIImageView, with overlayImageView: UIImageView) {
var images: [UIImage]!
var delayTime: Double!
guard let overlayImage = overlayImageView.image else {
print("Could not get top / overlay image!")
return
}
if let imgs = animatedImageView.image?.images {
// the image view is using .image = animatedImage
// unwrap the duration
if let dur = animatedImageView.image?.duration {
images = imgs
delayTime = dur / Double(images.count)
} else {
print("Image view is using an animatedImage, but could not get the duration!" )
return
}
} else if let imgs = animatedImageView.animationImages {
// the image view is using .animationImages
images = imgs
delayTime = animatedImageView.animationDuration / Double(images.count)
} else {
print("Could not get images array!")
return
}
// we now have a valid [UIImage] array, and
// a valid inter-frame duration, and
// a valid "overlay" UIImage
// generate unique file name
let destinationFilename = String(NSUUID().uuidString + ".gif")
// create empty file in temp folder to hold gif
let destinationURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(destinationFilename)
// metadata for gif file to describe it as an animated gif
let fileDictionary = [kCGImagePropertyGIFDictionary : [kCGImagePropertyGIFLoopCount : 0]]
// create the file and set the file properties
guard let animatedGifFile = CGImageDestinationCreateWithURL(destinationURL as CFURL, UTType.gif.identifier as CFString, images.count, nil) else {
print("error creating file")
return
}
CGImageDestinationSetProperties(animatedGifFile, fileDictionary as CFDictionary)
let frameDictionary = [kCGImagePropertyGIFDictionary : [kCGImagePropertyGIFDelayTime: delayTime]]
// use original size of gif
let sz: CGSize = images[0].size
let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(size: sz)
// loop through the images
// drawing the top/border image on top of each "frame" image with 80% alpha
// then writing the combined image to the gif file
images.forEach { img in
let combinedImage = renderer.image { ctx in
img.draw(at: .zero)
overlayImage.draw(in: CGRect(origin: .zero, size: sz), blendMode: .normal, alpha: 0.8)
}
guard let cgFrame = combinedImage.cgImage else {
print("error creating cgImage")
return
}
// add the combined image to the new animated gif
CGImageDestinationAddImage(animatedGifFile, cgFrame, frameDictionary as CFDictionary)
}
// done writing
CGImageDestinationFinalize(animatedGifFile)
print("New GIF created at:")
print(destinationURL)
print()
// do something with the newly created file...
// maybe move it to documents folder, or
// upload it somewhere, or
// save to photos library, etc
}
}
Notes:
the code is based on this article: How to Make an Animated GIF Using Swift
this should be considered Example Code Only!!! -- a starting-point for you, not a "production ready" solution.

How to display an image if variable equals a special number using swift 3?

I have three pictures (lvl1.png, lvl2.png, lvl3.png) and an variable (let level = 1). What should I do to display an image named 'lvl2' if level = 2, and when level = 3 I need to show the last image (lvl3.png)?
you can use:
let image = UIImage(named: "lvl\(level).png")
or
let image: UIImage!
switch level {
case 1:
image = UIImage(named: "lvl1.png")
case 2:
image = UIImage(named: "lvl2.png")
case 3:
image = UIImage(named: "lvl3.png")
default:
image = UIImage()
}
avatar.image = image
you can try
imgName = "lvl" + String(lavel)
imageView.image = UIImage(named: imgName)
Another alternative is to create a method that returns an image, like so:
func imageFor(level: Int) -> UIImage? {
let image = UIImage(named: "lvl\(currentLevel)")
return image
}
Usage:
var currentLevel = 1
let image = imageFor(level: currentLevel)
You can try
var level = 1 {
didSet {
self.imageview.image = UIImage(named: "lvl\(level)")
}
}
when you change the level the imageView will change automatically

Remove dots in the end of attributedText xcode

Heloo.. I am newbie here for ios, swift and xcode...
I have logic in swift file like:
class ExpandableHeaderView: UITableViewHeaderFooterView {
func customInit(menu: Menu, section: Int, delegate: ExpandableHeaderViewDelegate) {
//Create Attachment
let imageAttachment = NSTextAttachment()
var textAfterIcon: NSMutableAttributedString
switch menu {
case .HOME:
imageAttachment.image = UIImage(named:"home")
textAfterIcon = NSMutableAttributedString(string: " Home")
:
}
//Set bound to reposition
let imageOffsetY:CGFloat = -3.0;
imageAttachment.bounds = CGRect(
x: 0,
y: imageOffsetY,
width: imageAttachment.image!.size.width,
height: imageAttachment.image!.size.height)
//Create string with attachmen
let attachmentString = NSAttributedString(attachment: imageAttachment)
//Initialize mutable string
let completeText = NSMutableAttributedString(string: "")
//Add image to mutable string
completeText.append(attachmentString)
//Add your text to mutable string
completeText.append(textAfterIcon)
self.textLabel?.lineBreakMode = NSLineBreakMode.byTruncatingTail
self.textLabel?.attributedText = completeText
:
}
}
But I got result like:
I have added for all NSLineBreakMode.by*, but no one it is prefect to show image and the label: [image] Home
How can I remove 3 dots in the end for textLabel?.attributedText?
It was really confused me...
So, it is my pleasure for anyone can help me...
env:
xCode v. 9.3
I would not consider truncating your labels by "..." as a problem. You should firstly resolve correct sizing of your labels.

How to clean node material diffuse content memory in SceneKit?

In one of my app. i am facing issue of app crash because of i am unable to clean memory of node material diffuse content. when i am trying load node at that time memory is keeping up so i want clear memory whenever remove node from parent. please suggest approbate solution.
Here is below my code:
let recomondationView = viewRecomodation as! THARRecomondationsView
planeGeoMetryP1.firstMaterial?.diffuse.contents = UIImage.imageWithView(view: recomondationView)
oldAnnotationNode.name = name
oldAnnotationNode.geometry = planeGeoMetryP1
let billboardConstraint = SCNBillboardConstraint()
billboardConstraint.freeAxes = SCNBillboardAxis.Y
self.constraints = [billboardConstraint]
self.addChildNode(oldAnnotationNode)
Here is method of convert UIView to UIImage
extension UIImage {
class func imageWithView(view: UIView) -> UIImage {
var image = UIImage()
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, 1.0)
let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
image = renderer.image { ctx in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}
UIGraphicsEndImageContext()
return image
}
}
Here is the code that i am using to remove node from parent
if let index = self.sceneNode?.childNodes.index(of: locationNode) {
self.sceneNode?.childNodes[index].geometry = nil
self.sceneNode?.childNodes[index].removeFromParentNode()
}

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