How to convert NSView to CALayer? - objective-c

I have made a video player,and images are draw in a NSView.Have any method to convert NSView to CALayer?I try to use layer-hosting view,but developer document said can not add any subviews to layer-hosting view.Anyone can give me some suggestions?This code can run in OSX 10.6.8,but OSX 10.7 and 10.8.
mDisplayView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
mDisplayLayer = [[CALayer layer] retain];
[mDisplayView setLayer:mDisplayAudioLayer];
[mDisplayView setWantsLayer:YES];
[mDisplayView addSubview:mContentView];
[mRootLayer addSublayer:mDisplayAudioLayer];
The image of video had been drawn on mContentView.I just need find a way to make the mContentView into CALayer,is that possible?

You do not convert a view to a layer - you either draw into a view traditionally by overriding drawRect: or you switch it to layer-backed view and use CoreAnimation.
Best to start here to understand the basic concepts involved:
Core Animation Programming Guide

Related

cocoa - draw image in NSView

I'm having difficulties understanding why the below code doesn't work, what I want to achieve is an image being displayed in the top left corner of a NSView, but nothing is showing...
NSImage *map0 = [NSImage imageNamed:#"map0.png"];
NSRect rect = NSMakeRect(0, 0, 400, 400);
[map0 drawInRect:rect fromRect:NSZeroRect operation:NSCompositeSourceAtop fraction:1.0f];
[map drawRect:rect];
EDIT:
map is the NSView into which I would like to draw the image into
You never call drawRect: directly. This routine has various pre-conditions that are provided by Cocoa, such as the creation of a CGContextRef. You implement drawRect:. Cocoa calls it.
Your drawInRect:fromRect:operation:fraction: call should be put into the drawRect: of map, which should be a subclass of NSView. This specific problem is usually better solved with an NSImageView rather than a custom NSView, but if the drawing is more complex, then a custom NSView is appropriate.

How to add a CALayer to an NSView on Mac OS X

I'm trying to learn how to use and implement CALayer in a Mac Objective-C application, but I can't seem to probably do the most basic thing - add a new layer and set its background colour/frame size. Can anyone see what is wrong with my code?
CALayer *layer = [CALayer layer];
[layer setFrame:CGRectMake(0, 0, 100, 100)];
[layer setBackgroundColor:CGColorCreateGenericRGB(1.0, 0.0, 0.0, 1.0)];
[self.layer addSublayer:layer];
[layer display];
I put this in the - (void)drawRect:(NSRect)rect method of my custom NSView subclass, but when I run the application, it just shows a blank view, with no background colour or evidence of the layer I created.
First of all, you don't want to add a layer in the drawRect: method of a view, this gets called automatically by the system and you'd probably end up with a lot more layers than you actually want. initWithFrame: or initWithCoder: (for views that are in a nib file) are better places to initialize your layer hierarchy.
Furthermore, NSViews don't have a root layer by default (this is quite different from UIView on iOS). There are basically two kinds of NSViews that use a layer: layer-backed views and layer-hosting views. If you want to interact with the layer directly (add sublayers etc.), you need to create a layer-hosting view.
To do that, create a CALayer and call the view's setLayer: method. Afterwards, call setWantsLayer:. The order is important, if you'd call setWantsLayer: first, you'd actually create a layer-backed view.
You need to make a call to the "setWantsLayer" method.
Check out the following documentation for the description for setWantsLayer:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html
In a nutshell, your view needs to be layer-hosting view. Because it is a layer-hosting view, you should interact with the layer, and NOT interact with the view itself and don't add subviews to it.
[self setLayer:[CALayer new]];
[self setWantsLayer:YES]; // the order of setLayer and setWantsLayer is crucial!
[self.layer setBackgroundColor:[backgroundColor CGColor]];
Put this out of the drawRect. I normally put my layer setup in either the init method or the viewDidLoad.
Otherwise anytime the view is drawn a new layer is added and allocated. Also I've never used the [layer display] line before. The docs actually tell you not to call this method directly.
Updated info (Swift): first call view.makeBackingLayer() then set wantsLayer to true.
https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer

How to interact with layer-backed views on the Mac

I am designing a user interface containing several labels and text fields. I would like to style the UI like this:
setting a background pattern for the content view of my NSWindow
adding a custom icon to the background in the upper left corner
I solved the first problem by making the content view a layer-backed view as described in Apple's documentation of NSView:
A layer-backed view is a view that is backed by a Core Animation layer. Any drawing done by the view is the cached in the backing layer. You configured a layer-backed view by simply invoking setWantsLayer: with a value of YES. The view class will automatically create the a backing layer for you, and you use the view class’s drawing mechanisms. When using layer-backed views you should never interact directly with the layer.
A layer-hosting view is a view that contains a Core Animation layer that you intend to manipulate directly. You create a layer-hosting view by instantiating an instance of a Core Animation layer class and setting that layer using the view’s setLayer: method. After doing so, you then invoke setWantsLayer: with a value of YES. When using a layer-hosting view you should not rely on the view for drawing, nor should you add subviews to the layer-hosting view.
and then generating a CGColorRef out of a CGPattern which draws my CGImage:
NSView *mainView = [[self window]contentView];
[mainView setWantsLayer:YES];
To set the background image as a pattern I used the answer from How to tile the contents of a CALayer here on SO to get the first task done.
However for the second task, adding the icon I used the code below:
CGImageRef iconImage = NULL;
NSString *path = [[NSBundle mainBundle] pathForResource:#"icon_128" ofType:#"png"];
if(path != nil) {
NSURL *imageURL = [NSURL fileURLWithPath:path];
provider = CGDataProviderCreateWithURL((CFURLRef)imageURL);
iconImage = CGImageCreateWithPNGDataProvider(provider,NULL,FALSE,kCGRenderingIntentDefault);
CFRelease(provider);
}
CALayer *iconLayer = [[CALayer alloc] init];
// layer is the mainView's layer
CGRect layerFrame = layer.frame;
CGFloat iconWidth = 128.f;
iconLayer.frame = CGRectMake(0.f, CGRectGetHeight(layerFrame)-iconWidth, 128.f, 128.f);
iconLayer.contents = (id)iconImage;
CGImageRelease(iconImage);
[layer insertSublayer:iconLayer atIndex:0];
[iconLayer release];
The Questions
I am not sure if I am violating Apple's restrictions concerning layer-backed views that you should never interact directly with the layer. When setting the layer's background color I am interacting directly with the layer or am I mistaken here?
I have a bad feeling about interacting with the layer hierarchy of a layer-backed view directly and inserting a new layer like I did for my second task. Is this possible or also violating Apple's guidelines? I want to point out that this content view of course has several subviews such as labels, a text view and buttons.
It seems to me that just using one single layer-hosting NSView seems to be the cleanest solution. All the text labels could then be added as CATextLayers etc. However if I understand Apple's documentation correctly I cannot add any controls to the view anymore. Would I have to code all the controls myself in custom CALayers to get it working? Sounds like reinventing the wheel de luxe. I also have no idea how one would code a NSTextField solely in CoreAnimation.
Any advice on how split designing user interfaces with CoreAnimation and standard controls is appreciated.
Please note that I am talking about the Mac here.
no layer backing needed IMHO:
for 1. I do a pattern image
NSImage *patternImage = [NSImage imageNamed:#"pattern"];
[window setBackgroungdColor:[NSColor colorWithPatternImage:patternImage]];
for 2. add an NSImageView as a subview of the contentview
NSImageView *v = ...
[[window contentView] addSubview:v];
on mac some views dont respond nicely IF layer backed
:: e.g. pdfview
Make a superview container A. Add a subview B to A for all your NSView needs (buttons, etc.). Add a subview C to A for all your Core Animation needs.
Edit:
Even better: use superview A for all your NSView needs and one subview C for your Core Animation needs, ignoring view B altogether.

Rendering NSView containing some CALayers to an NSImage

I have an NSView that contains an NSScrollView containing a CALayer-backed NSView. I've tried all the usual methods of capturing an NSView into an NSImage (using -dataWithPDFInsideRect, NSBitmapImageRep's -initWithFocusedViewRect, etc.) However, all these methods treat the CALayer-backed NSView as if it doesn't exist. I've already seen this StackOverflow post, but it was a question about rendering just a CALayer tree to an image, not an NSView containing both regular NSView's and layer-backed views.
Any help is appreciated, thanks :)
The only way I found to do this is to use the CGWindow API's, something like:
CGImageRef cgimg = CGWindowListCreateImage(CGRectZero, kCGWindowListOptionIncludingWindow, [theWindow windowNumber], kCGWindowImageDefault);
then clip out the part of that CGImage that corresponds to your view with
-imageByCroppingToRect.
Then make a NSImage from the cropped CGImage.
Be aware this won't work well if parts of that window are offscreen.
This works to draw a view directly to an NSImage, though I haven't tried it with a layer-backed view:
NSImage * i = [[NSImage alloc] initWithSize:[view frame].size];
[i lockFocus];
if ([view lockFocusIfCanDrawInContext:[NSGraphicsContext currentContext]]) {
[view displayRectIgnoringOpacity:[view frame] inContext:[NSGraphicsContext currentContext]];
[view unlockFocus];
}
[i unlockFocus];
NSData * d = [i TIFFRepresentation];
[d writeToFile:#"/path/to/my/test.tiff" atomically:YES];
[i release];
Have you looked at the suggestions in the Cocoa Drawing Guide? ("Creating a Bitmap")
To draw directly into a bitmap, create a new NSBitmapImageRep object with the parameters you want and use the graphicsContextWithBitmapImageRep: method of NSGraphicsContext to create a drawing context. Make the new context the current context and draw. This technique is available only in Mac OS X v10.4 and later.
Alternatively, you can create an NSImage object (or an offscreen window), draw into it, and then capture the image contents. This technique is supported in all versions of Mac OS X.
That sounds similar to the iOS solution I'm familiar with (using UIGraphicsBeginImageContext and UIGraphicsGetImageFromCurrentImageContext) so I'd expect it to work for your view.
Please have a look at the answer on this post: How do I render a view which contains Core Animation layers to a bitmap?
That approach worked for me under similar circumstances to your own.

Background image for a window in Cocoa framework

I am looking for a perfect solution to set a background image for a window in a cocoa application. I haven't found a solution to this, I am new in objective c, so please anyone help me...
A window in Cocoa has a root-level view called the "content view". This is the view that contains all the others in a window. By default, it's just a plain, blank NSView. But you could easily create your own custom NSView subclass, override the drawRect: method to draw your background image, and use that for your custom view.
However, it might just be easier to use a plain old NSImageView. The advantage of this is that you can set, for example, autosizing behavior to keep the image pinned to one corner (try this with Installer.app by resizing the installer window). You would also be able to make it semi-opaque so that the background shows through a bit. (Again, I'm thinking of Installer.app; your app could be totally different)
Hope that gets you going in the right direction!
Michael Vannorsdel suggests sublassing NSView for the purpose, and I quote:
You'd really be better off making an
NSView subclass and having it draw
the image you want in drawRect:.
- (void)awakeFromNib
{
myImage = [[NSImage alloc] init....
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect
{
NSSize isize = [myImage size];
[myImage drawInRect:[self bounds] fromRect:NSMakeRect(0.0, 0.0,
isize.width, isize.height) operation: NSCompositeCopy fraction:1.0];
}
Read that whole thread on cocoabuilder, it's quite instructive.