Add animated Gif image in Iphone UIImageView - objective-c

I need to load an animated Gif image from a URL in UIImageview.
When I used the normal code, the image didn't load.
Is there any other way to load animated Gif images?

UIImageView* animatedImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
animatedImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"image1.gif"],
[UIImage imageNamed:#"image2.gif"],
[UIImage imageNamed:#"image3.gif"],
[UIImage imageNamed:#"image4.gif"], nil];
animatedImageView.animationDuration = 1.0f;
animatedImageView.animationRepeatCount = 0;
[animatedImageView startAnimating];
[self.view addSubview: animatedImageView];
You can load more than one gif images.
You can split your gif using the following ImageMagick command:
convert +adjoin loading.gif out%d.gif

This has found an accepted answered, but I recently came across the UIImage+animatedGIF UIImage extension. It provides the following category:
+[UIImage animatedImageWithAnimatedGIFURL:(NSURL *)url]
allowing you to simply:
#import "UIImage+animatedGIF.h"
UIImage* mygif = [UIImage animatedImageWithAnimatedGIFURL:[NSURL URLWithString:#"http://en.wikipedia.org/wiki/File:Rotating_earth_(large).gif"]];
Works like magic.

Here is the best solution to use Gif Image.
Add SDWebImage from Github in your project.
#import "UIImage+GIF.h"
_imageViewAnimatedGif.image= [UIImage sd_animatedGIFNamed:#"thumbnail"];

If you don't want to use 3rd party library,
extension UIImageView {
func setGIFImage(name: String, repeatCount: Int = 0 ) {
DispatchQueue.global().async {
if let gif = UIImage.makeGIFFromCollection(name: name, repeatCount: repeatCount) {
DispatchQueue.main.async {
self.setImage(withGIF: gif)
self.startAnimating()
}
}
}
}
private func setImage(withGIF gif: GIF) {
animationImages = gif.images
animationDuration = gif.durationInSec
animationRepeatCount = gif.repeatCount
}
}
extension UIImage {
class func makeGIFFromCollection(name: String, repeatCount: Int = 0) -> GIF? {
guard let path = Bundle.main.path(forResource: name, ofType: "gif") else {
print("Cannot find a path from the file \"\(name)\"")
return nil
}
let url = URL(fileURLWithPath: path)
let data = try? Data(contentsOf: url)
guard let d = data else {
print("Cannot turn image named \"\(name)\" into data")
return nil
}
return makeGIFFromData(data: d, repeatCount: repeatCount)
}
class func makeGIFFromData(data: Data, repeatCount: Int = 0) -> GIF? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
print("Source for the image does not exist")
return nil
}
let count = CGImageSourceGetCount(source)
var images = [UIImage]()
var duration = 0.0
for i in 0..<count {
if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
let image = UIImage(cgImage: cgImage)
images.append(image)
let delaySeconds = UIImage.delayForImageAtIndex(Int(i),
source: source)
duration += delaySeconds
}
}
return GIF(images: images, durationInSec: duration, repeatCount: repeatCount)
}
class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {
var delay = 0.0
// Get dictionaries
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifPropertiesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 0)
if CFDictionaryGetValueIfPresent(cfProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque(), gifPropertiesPointer) == false {
return delay
}
let gifProperties:CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self)
// Get delay time
var delayObject: AnyObject = unsafeBitCast(
CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)
}
delay = delayObject as? Double ?? 0
return delay
}
}
class GIF: NSObject {
let images: [UIImage]
let durationInSec: TimeInterval
let repeatCount: Int
init(images: [UIImage], durationInSec: TimeInterval, repeatCount: Int = 0) {
self.images = images
self.durationInSec = durationInSec
self.repeatCount = repeatCount
}
}
To use,
override func viewDidLoad() {
super.viewDidLoad()
imageView.setGIFImage(name: "gif_file_name")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
imageView.stopAnimating()
}
Make sure you add the gif file in the project, not in the .xcassets folder.

Check this link
https://github.com/mayoff/uiimage-from-animated-gif/blob/master/uiimage-from-animated-gif/UIImage%2BanimatedGIF.h
and import these clases UIImage+animatedGIF.h,UIImage+animatedGIF.m
Use this code
NSURL *urlZif = [[NSBundle mainBundle] URLForResource:#"dots64" withExtension:#"gif"];
NSString *path=[[NSBundle mainBundle]pathForResource:#"bar180" ofType:#"gif"];
NSURL *url=[[NSURL alloc] initFileURLWithPath:path];
imageVw.image= [UIImage animatedImageWithAnimatedGIFURL:url];
Hope this is helpfull

This doesn't meet the requirement of using a UIImageView, but maybe this would simplify things for you. Have you considered using a UIWebView?
NSString *gifUrl = #"http://gifs.com";
NSURL *url = [NSURL URLWithString: gifUrl];
[webView loadRequest: [NSURLRequest requestWithURL:url]
If you want, instead of linking to a URL that requires Internet, you could import an HTML file into your Xcode project and set the root in the string.

Here is an interesting library: https://github.com/Flipboard/FLAnimatedImage
I tested the demo example and it's working great. It's a child of UIImageView. So I think you can use it in your Storyboard directly as well.
Cheers

I know that an answer has already been approved, but its hard not to try to share that I've created an embedded framework that adds Gif support to iOS that feels just like if you were using any other UIKit Framework class.
Here's an example:
UIGifImage *gif = [[UIGifImage alloc] initWithData:imageData];
anUiImageView.image = gif;
Download the latest release from https://github.com/ObjSal/UIGifImage/releases
-- Sal

If you must load the gif image from URL, you can always embed the gif in an image tag in a UIWebView.

SWIFT 3
Here is the update for those who need the Swift version!.
A few days ago i needed to do something like this. I load some data from a server according specific parameters and in the meanwhile i wanted to show a different gif image of "loading". I was looking for an option to do it with an UIImageView but unfortunately i didn't find something to do it without splitting the .gif images. So i decided to implement a solution using a UIWebView and i want to shared it:
extension UIView{
func animateWithGIF(name: String){
let htmlString: String = "<!DOCTYPE html><html><head><title></title></head>" +
"<body style=\"background-color: transparent;\">" +
"<img src=\""+name+"\" align=\"middle\" style=\"width:100%;height:100%;\">" +
"</body>" +
"</html>"
let path: NSString = Bundle.main.bundlePath as NSString
let baseURL: URL = URL(fileURLWithPath: path as String) // to load images just specifying its name without full path
let frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
let gifView = UIWebView(frame: frame)
gifView.isOpaque = false // The drawing system composites the view normally with other content.
gifView.backgroundColor = UIColor.clear
gifView.loadHTMLString(htmlString, baseURL: baseURL)
var s: [UIView] = self.subviews
for i in 0 ..< s.count {
if s[i].isKind(of: UIWebView.self) { s[i].removeFromSuperview() }
}
self.addSubview(gifView)
}
func animateWithGIF(url: String){
self.animateWithGIF(name: url)
}
}
I made an extension for UIView which adds a UIWebView as subview and displays the .gif images just passing its name.
Now in my UIViewController i have a UIView named 'loadingView' which is my 'loading' indicator and whenever i wanted to show the .gif image, i did something like this:
class ViewController: UIViewController {
#IBOutlet var loadingView: UIView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
configureLoadingView(name: "loading.gif")
}
override func viewDidLoad() {
super.viewDidLoad()
// .... some code
// show "loading" image
showLoadingView()
}
func showLoadingView(){
loadingView.isHidden = false
}
func hideLoadingView(){
loadingView.isHidden = true
}
func configureLoadingView(name: String){
loadingView.animateWithGIF(name: "name")// change the image
}
}
when I wanted to change the gif image, simply called the function configureLoadingView() with the name of my new .gif image and calling showLoadingView(), hideLoadingView() properly everything works fine!.
BUT...
... if you have the image splitted then you can animate it in a single line with a UIImage static method called UIImage.animatedImageNamed like this:
imageView.image = UIImage.animatedImageNamed("imageName", duration: 1.0)
From the docs:
This method loads a series of files by appending a series of numbers to the base file name provided in the name parameter. All images included in the animated image should share the same size and scale.
Or you can make it with the UIImage.animatedImageWithImages method like this:
let images: [UIImage] = [UIImage(named: "imageName1")!,
UIImage(named: "imageName2")!,
...,
UIImage(named: "imageNameN")!]
imageView.image = UIImage.animatedImage(with: images, duration: 1.0)
From the docs:
Creates and returns an animated image from an existing set of images. All images included in the animated image should share the same size and scale.

With Swift and KingFisher
lazy var animatedPart: AnimatedImageView = {
let img = AnimatedImageView()
if let src = Bundle.main.url(forResource: "xx", withExtension: "gif"){
img.kf.setImage(with: src)
}
return img
}()

You Can use https://github.com/Flipboard/FLAnimatedImage
#import "FLAnimatedImage.h"
NSData *dt=[NSData dataWithContentsOfFile:path];
imageView1 = [[FLAnimatedImageView alloc] init];
FLAnimatedImage *image1 = [FLAnimatedImage animatedImageWithGIFData:dt];
imageView1.animatedImage = image1;
imageView1.frame = CGRectMake(0, 5, 168, 80);
[self.view addSubview:imageView1];

Swift 3:
As suggested above I'm using FLAnimatedImage with an FLAnimatedImageView. And I'm loading the gif as a data set from xcassets. This allows me to provide different gifs for iphone and ipad for appearance and app slicing purposes. This is far more performant than anything else I've tried. It's also easy to pause using .stopAnimating().
if let asset = NSDataAsset(name: "animation") {
let gifData = asset.data
let gif = FLAnimatedImage(animatedGIFData: gifData)
imageView.animatedImage = gif
}

Related

Return type of UIImage in Swift [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I have a function which returns image if condition is satisfied else it returns nil
- (UIImage *)getProfilePic{
if (doc.userProperties != nil) {
UIImage *image = [UIImage imageNamed:#"placeholder.png"];
return image;
}
else {
return nil;
}
}
I want to convert this is in swift. I have tried this but it shows error while returning nil and also it crashes by showing error as unwrapping nil.
func getProfilePic(){
var image : UIImage?
if doc!.userProperties != nil {
image = UIImage(named: "placeholder.png")!
return image!
}
else {
return nil
}
}
at "return nil" line it shows nil is not compatible with return type ' uiimage'
func getProfilePic(){
var image : UIImage?
if doc!.userProperties != nil {
image = UIImage(named: "placeholder.png")!
return image!
}
else {
return nil
}
}
Right, so there are a few problems here. First, your code doesn't have a return type. If we ignore the body of your method and look just at the signature, the Objective-C equivalent would look like this:
- (void)getProfilePic;
So Swift & Objective-C would be complaining about the same thing here: what you're trying to return and the declared return type of the method do not match.
In case it's helpful since you seem perhaps more familiar with Objective-C than Swift, here's what your Swift method would look like if we translated it back into Objective-C:
- (void)getProfilePic {
UIImage *image;
if (doc.userProperties) {
image = UIImage(named: #"placeholder.png");
return image;
}
else {
return nil;
}
}
And again, this would generate the same or similar compile time warnings or errors, because the return type does not match the method signature. But Objective-C would not crash for unwrapping nil (but Swift will).
What you're actually trying to return is a UIImage?, so we need to update our method signature.
func getProfilePic() -> UIImage? {
if doc?.userProperties != nil {
return UIImage(named: "placeholder.png")
}
return nil
}
Assuming that userProperties holds perhaps a URL to an image you want to download or maybe the image itself, in the future we're going to want a slightly different construct... something more like this:
func getProfilePic() -> UIImage? {
guard let userProperties = doc?.userProperties else {
return nil
}
// extract the image from userProperties and return it
}
Try this code sample :
func getProfilePic() -> UIImage? {
let imageName = "placeholder.png"
var image: UIImage?
if (doc.userProperties != nil) {
image = UIImage(named: imageName)
}
return image
}

How to store multiple UIImages within each index of an array of UIImages in Swift 2.0?

first I apologize if the title is not descriptive. Please free feel to change it more appropriate to my issue.
So basically, I have an array of UIImages called eventImages.
In obj-C, I declared it and initialized it like so:
NSArray *eventImages;
eventImages = #[ [UIImage imageNamed:#"lion_dance.jpg"],
[UIImage imageNamed:#"ivan_cheong.jpg"],
[UIImage imageNamed:#"tien_nguyen.jpg"],
[UIImage imageNamed:#"step_up.jpg"],
#[ [UIImage imageNamed:#"dj_happee.jpg"], [UIImage imageNamed:#"adam_cease.jpg"], [UIImage imageNamed:#"ao_dai.jpg"] ],
[UIImage imageNamed:#"california_summer.jpg"],
[UIImage imageNamed:#"question_mark"] ];
As you can see, at the 4th index, I am able to store an inner array UIImages.
I would like to achieve this in Swift 2.0
Here's what I've done so far:
var eventImages: [UIImage]?
eventImages = [UIImage(named: "lion_dance.jpg")!,
UIImage(named: "ivan_cheong.jpg")!, UIImage(named: "tien_nguyen.jpg")!,
UIImage(named: "step_up.jpg")!, [UIImage(named: "dj_happee.jpg")!,
UIImage(named: "adam_cease.jpg")!, UIImage(named: "ao_dai.jpg")!],
UIImage(named: "california_summer.jpg")!, UIImage(named: "question_mark")!]
However, the compiler is giving me the error:
Contextual type 'UIImage' cannot be used with array literal
How can I achieve in Swift what I achieved in Obj-C?
Thanks
You can just do it like:
var eventImages = [UIImage(named: "lion_dance.jpg")!,
UIImage(named: "ivan_cheong.jpg")!, UIImage(named: "tien_nguyen.jpg")!,
UIImage(named: "step_up.jpg")!, [UIImage(named: "dj_happee.jpg")!,
UIImage(named: "adam_cease.jpg")!, UIImage(named: "ao_dai.jpg")!],
UIImage(named: "california_summer.jpg")!, UIImage(named: "question_mark")!]
If you declare array type to by UIImage you cannot add array of UIImage ([UIImage]) because type is different. However if you skip type declaration, Swift will figure it out and allow this array to exist.
The type of array would be NSObject I think, then you can just cast it to access images:
class SomeClass {
var eventImages = [[UIImage()], UIImage(), UIImage(), UIImage(), [UIImage(), UIImage(), UIImage()]]
func doSomething() {
if let singleImage = eventImages[1] as? UIImage {
// Access single image
}
if let imageArray = eventImages[4] as? [UIImage] {
// Access image array
}
}
}
You can Follow the below steps to store multiple images in array Swift 2.0 like this,
---> First prepare or declare array object like,
var eventImages : NSArray = NSArray()
---> Now Store whatever images u want in your array object like,
eventImages = ["1.png","2.png","3.png", NSArray(objects: "4.png", "5.png")]
---> Now Check whether its array or string in array and get image name and assigned it to image view
if let arr = eventImages.objectAtIndex(2) as? NSString // means it check whether its string
{
self.imgVIew.image = UIImage(named: arr as String)
}
else // if not string then take from array like this
{
self.imgVIew.image = UIImage(named: eventImages.objectAtIndex(3).objectAtIndex(1) as! String)
}
I hope you get your image and assign it to image view...enjoy..it

How to resume a CAAnimation after coming back from multitasking

in my app i have an array of CALayer that I have animated along a bezierPath. When I close and reopen the app my layers are not animating and not in the same position as before closing the app. I have implemented two methods, pauseLayer and resumeLayer that works when I trigger them with two buttons inside my app but they won't work after closing the app. The code is the following
- (void)pauseLayers{
for(int y=0; y<=end;y++)
{
CFTimeInterval pausedTime = [car[y] convertTime:CACurrentMediaTime() fromLayer:nil];
car[y].speed = 0.0;
car[y].timeOffset = pausedTime;
standardUserDefaults[y] = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults[y]) {
[standardUserDefaults[y] setDouble:pausedTime forKey:#"pausedTime"];
[standardUserDefaults[y] synchronize];
}
NSLog(#"saving positions");
}
}
-(void)resumeLayers
{
for(int y=0; y<=end;y++)
{
standardUserDefaults[y] = [NSUserDefaults standardUserDefaults];
car[y].timeOffset = [standardUserDefaults[y] doubleForKey:#"pausedTime"];
CFTimeInterval pausedTime = [car[y] timeOffset];
car[y].speed = 1.0;
car[y].timeOffset = 0.0;
car[y].beginTime = 0.0;
CFTimeInterval timeSincePause = [car[y] convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
car[y].beginTime = timeSincePause;
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
mosquitosViewController *mvc = [[mosquitosViewController alloc] init];
[mvc pauseLayers];
}
The problem with what you are trying to do above is that you are creating a completely new instance of your view controller, which is not the one that was showing onscreen. That's why nothing happens when you send the pauseLayers message.
What you should do is register to receive notifications for when your app goes to and comes from the background and call the appropriate methods (pauseLayers and resumeLayers) when that notification arrives.
You should add the following code somewhere in your mosquitosViewController implementation (I usually do so in viewDidLoad):
// Register for notification that app did enter background
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(pauseLayers)
name:UIApplicationDidEnterBackgroundNotification
object:[UIApplication sharedApplication]];
// Register for notification that app did enter foreground
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(resumeLayers)
name:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication]];
I write a Swift 4 version extension based on #cclogg and #Matej Bukovinski answers from this thread. All you need is to call layer.makeAnimationsPersistent()
Full Gist here: CALayer+AnimationPlayback.swift, CALayer+PersistentAnimations.swift
Core part:
public extension CALayer {
static private var persistentHelperKey = "CALayer.LayerPersistentHelper"
public func makeAnimationsPersistent() {
var object = objc_getAssociatedObject(self, &CALayer.persistentHelperKey)
if object == nil {
object = LayerPersistentHelper(with: self)
let nonatomic = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
objc_setAssociatedObject(self, &CALayer.persistentHelperKey, object, nonatomic)
}
}
}
public class LayerPersistentHelper {
private var persistentAnimations: [String: CAAnimation] = [:]
private var persistentSpeed: Float = 0.0
private weak var layer: CALayer?
public init(with layer: CALayer) {
self.layer = layer
addNotificationObservers()
}
deinit {
removeNotificationObservers()
}
}
private extension LayerPersistentHelper {
func addNotificationObservers() {
let center = NotificationCenter.default
let enterForeground = NSNotification.Name.UIApplicationWillEnterForeground
let enterBackground = NSNotification.Name.UIApplicationDidEnterBackground
center.addObserver(self, selector: #selector(didBecomeActive), name: enterForeground, object: nil)
center.addObserver(self, selector: #selector(willResignActive), name: enterBackground, object: nil)
}
func removeNotificationObservers() {
NotificationCenter.default.removeObserver(self)
}
func persistAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = layer.animation(forKey: key) {
persistentAnimations[key] = animation
}
}
}
func restoreAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = persistentAnimations[key] {
layer.add(animation, forKey: key)
}
}
}
}
#objc extension LayerPersistentHelper {
func didBecomeActive() {
guard let layer = self.layer else { return }
restoreAnimations(with: Array(persistentAnimations.keys))
persistentAnimations.removeAll()
if persistentSpeed == 1.0 { // if layer was playing before background, resume it
layer.resumeAnimations()
}
}
func willResignActive() {
guard let layer = self.layer else { return }
persistentSpeed = layer.speed
layer.speed = 1.0 // in case layer was paused from outside, set speed to 1.0 to get all animations
persistAnimations(with: layer.animationKeys())
layer.speed = persistentSpeed // restore original speed
layer.pauseAnimations()
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"1");
mosquitosViewController *mvc = [[mosquitosViewController alloc] init];
[mvc pauseLayers];
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
See my answer to this post for details on how to restart an animation after multitasking:
Restoring animation where it left off when app resumes from background

How to copy textField to OSX clipboard?

I'm stuck here. I know how to copy and paste on the iPhone side of things but how can I copy contents from a textField to the global clipboard in OSX. I've been searching the web but there are really no examples. So let me explain in detail what I'm trying to accomplish. I have a NSTextField named helloField and I want to be able to copy the contents of this helloField to the global pasteboard by pressing a button. How can this be done and is there certain libraries I need? Thanks.
On iOS
[UIPasteboard generalPasteboard].string = helloField.text;
On OSX
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:helloField.stringValue forType:NSStringPboardType];
On macOS and Swift 3.x
let pasteBoard = NSPasteboard.general()
pasteBoard.clearContents()
pasteBoard.writeObjects([text as NSString])
For Swift 5
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString("string to copy", forType: .string)
Code to copy a string to the clipboard:
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:copiedString forType:NSPasteboardTypeString];
NSStringPboardType is deprecated. There's a note in NSPasteboard.h about pboard types:
Use of pboard types should be replaced with use of UTIs. Pboard types will be deprecated in a future release.
Also in the header file:
APPKIT_EXTERN NSString *const NSPasteboardTypeString NS_AVAILABLE_MAC(10_6); // Replaces NSStringPboardType
...
APPKIT_EXTERN NSString *NSStringPboardType; //Use NSPasteboardTypeString
For Cocoa macOS in Swift 3:
let pasteBoard = NSPasteboard.general()
pasteBoard.clearContents()
pasteBoard.setString("something", forType: NSPasteboardTypeString)
You can create an extension for your String which supports iOS and macOS:
extension String {
func copy() {
#if os(macOS)
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(self, forType: .string)
#else
UIPasteboard.general.string = self
#endif
}
}
Clipboard.set("some text")
class:
import AppKit
public class Clipboard {
public static func set(text: String?) {
if let text = text {
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setString(text, forType: .string)
}
}
#available(macOS 10.13, *)
public static func set(url: URL?) {
guard let url = url else { return }
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setData(url.dataRepresentation, forType: .URL)
}
#available(macOS 10.13, *)
public static func set(urlContent: URL?) {
guard let url = urlContent,
let nsImage = NSImage(contentsOf: url)
else { return }
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.writeObjects([nsImage])
}
public static func clear() {
NSPasteboard.general.clearContents()
}
}

How do I take a "screenshot" of an NSView?

I need to take the contents of an NSView and put them in an NSImage, for an experimental project. Is this possible? I did some Googling, tried two methods that I found - but they didn't really work. Any suggestions?
From WWDC 2012 Session 245 (translated to Swift):
let viewToCapture = self.window!.contentView!
let rep = viewToCapture.bitmapImageRepForCachingDisplay(in: viewToCapture.bounds)!
viewToCapture.cacheDisplay(in: viewToCapture.bounds, to: rep)
let img = NSImage(size: viewToCapture.bounds.size)
img.addRepresentation(rep)
[[NSImage alloc] initWithData:[view dataWithPDFInsideRect:[view bounds]]];
let dataOfView = view.dataWithPDFInsideRect(view.bounds)
let imageOfView = NSImage(data: dataOfView)
NSView.bitmapImageRepForCachingDisplay() (mentioned in this answer) doesn't render the colors correctly on some views.
CGWindowListCreateImage() works perfectly for me.
Here's my implementation:
extension NSView {
#objc func takeScreenshot() -> NSImage? {
let screenRect = self.rectInQuartzScreenCoordinates()
guard let window = self.window else {
assert(false); return nil
}
let windowID = CGWindowID(window.windowNumber)
guard let screenshot = CGWindowListCreateImage(screenRect, .optionIncludingWindow, windowID, []) else {
assert(false); return nil
}
return NSImage(cgImage: screenshot, size: self.frame.size)
}
}
This code uses a method NSView.rectInQuartzScreenCoordinates(). To implement it you'll first have to convert the bounds of your view to screenCoordinates using NSView and NSWindow methods and then you need to flip the coordinates like this.