How to interact with layer-backed views on the Mac

2019-05-06 04:29发布

问题:

I am designing a user interface containing several labels and text fields. I would like to style the UI like this:

  1. setting a background pattern for the content view of my NSWindow
  2. 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

  1. 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?
  2. 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.
  3. 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.

回答1:

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



回答2:

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.