mac os x High Sierra+CALayer+layoutManager+NSview

Hi!


I'm having a catch-22 kind of issue, need some perspective on it.

Just updated to High Sierra, which wierdly forced a new version of Xcode onto my System (I was already running the latest)


anyway, i hit compile, and I get a few wierd runtime errors.


one of them, happens when I add a Layout manager to my View's layer (a CALayer, backing the view)

I learned some time ago, that you destroy the view, and render it worthless if you change the view's layer, a great many things particular to that view are wiped out, never to come back. and so instead, I add a subLayer, and I constrain it to the size of the base layer, and work from that.


Now I can't do that, because without a layoutManager, the view's layer can't address the constraints, so my sub layer sits at the bottom left of the view... pretty worthless.


see? catch-22. you can't assign a layout manager to the layer backing the view, so you can't control the layout of any sublayers of that layer. How is this supposed to work? Because it doesn't take any effort to see that Apple doesn't intend all CALayer based Desktop solutions to lose the ability to be laid out.

Replies

I find the documentation on what we are "allowed to do" with Core Animation on macOS a bit confusing. In the Appkit release notes it says:


> In OS X v10.8 and later, it is recommended that you minimize your use of layer hierarchies and just use layer-backed views. The layer redraw policies introduced in that version of OS X let you customize the behavior of your layer-backed views and still get the kind of performance you might have gotten previously using standalone layers.


But when you use a "layer backed view" they basically say, "don't touch anything" for the most part. In the 10.13 release notes they say:


>macOS 10.13 is more consistent around updates to properties of views' layers (that is, layers that are the .layer of some NSView, however created). As before, if an application modifies such a layer by changing any of the following properties, the behavior of the application is undefined: bounds, position, zPosition, anchorPoint, anchorPointZ, transform, affineTransform, frame, hidden, geometryFlipped, masksToBounds, opaque, compositingFilter, filters, shadowColor, shadowOpacity, shadowOffset, shadowRadius, shadowPath, layoutManager.


In addition, it is illegal (and ineffective) to change the delegate of a view's layer unless that view either returns a custom layer from -makeBackingLayer or had a custom layer set via -setLayer:. If a view does not have such a custom layer, the identity of its layer's delegate is undefined and should not be relied upon.


As before, for best results, a view that modifies any other properties of its layer should also faithfully implement drawRect: to reflect such modifications, since not all layer properties can be automatically represented in all drawing circumstances. For instance, if a view's -updateLayer implementation changes the border properties of its layer, its -drawRect: should draw an equivalent border.

------

Not sure what the benefit is to accessing properties on CALayer and changing them, if you have to override and do the drawing in drawRect too anyway from the view.

Can you override makeBackingLayer and return a CALayer with your layout manager set on that?

Well,

mr Macho Man ***** Savage...

I cannot solve my problem by overriding makeBackingLayer.


see, my view is an NSView subclass. It is intended to be able to switch ViewControllers on the fly. Each ViewController sets up the view in any way it sees fit... for instance: one might load a view hierarchy from an XIB, another might build an entire UI from CALayers, still others might draw into the view itself.


So the first hurdle, is to get the Layer in the view, to support having a LayoutManager set. This is critical, because it's literally the only way to control the layout of any subLayers in a layer hierarchy. If you can't put a layout manager on the top-level layer, then the whole thing is shot.


the SECOND Hurdle, is putting the view back into mint condition.

it used to be possible to do this. all you had to do is add the layout manager to the view's backing layer. Then you add all of your locally specific stuff as sublayers to that layer. Then when the ViewController is pulled from the View, you just remove the sublayers.

Now... however: you cannot add a layout manager to the backing layer, and if you change the backing layer, you can't put the view back into mint condition.

loading XIBs, doesn't work, and niether does drawing into the view... even AFTER you turn off support for layers (wantsLayer = false)


Once you change the backing layer of a view, you've killed that view.

>see, my view is an NSView subclass. It is intended to be able to switch ViewControllers on the fly. Each ViewController sets up the view in any way it sees fit...


You mean, you pass a single view instance between view controllers? Normally each view controller has its own instance and sets up its view.


As I previously mentioned I'm a little confused at what we are allowed to "touch" on the CALayer when layer backed. Can you make a generic view subclass of your own (this can be set in IB if needed)?



The bit I read from the documentation I posted above doesn't mention anything about setting a layer-backed view's layout manager, not sure if this is mentioned in the CA programming guide. But could you add a sublayer to the view's layer that fills its entire contents and set yourself as the layout manager of that? Maybe something like this?


@interface MyLayer : CALayer

//Have contentLayer be a CALayer that wraps everything, and add all sublayer to this layer?
@property CALayer *contentLayer;

@end

@implementation MyLayer

//Create and add contentLayer as a sublayer when you set up.

- (void)layoutSublayers
{
  [super lauyoutSublayers];
   //have content layer fill  this entire laeer.
   self.contentLayer.frame =   //set the frame...
}


Then have your view subclass do something like this?


@interface LayerBackedView : NSView

@end

@implementation LayerBackedView

-(CALayer*)makeBackingLayer
{
    MyLayer *theLayer = [MyLayer alloc]init];
    theLayer.contentLayer.layoutManager = //...set it to whatever.
    return theLayer;
}
@end



And add all your sublayers to the contentLayer property instead of the view's .layer?