What is the CALayer equivalent to UIView's sizeToFit-method? - uiview

I'm searching for a way to let a CALayer resize itself whenever its sublayers change (which means either when the bounds of any sublayer change or when the sublayer array itself changes).
When i worked with views before, i managed that through implementing sizeThatFits in my custom UIView subclass, which was called automatically by sizeToFit whenever the view's subviews changed.
Since CALayer has the sizeThatFits-equivalent-method preferredSize, i was surprised not to find a sizeToFit-equivalent.

I think you need to implement(override) the - (void)layoutSublayers
...
*Subclasses can override this to
* provide their own layout algorithm, which should set the frame of
* each sublayer
- (void)layoutSublayers
{
// self.frame = aNewSubLayer.bounds
// basically do algorithm for setting frame and bounds for this(self) layer and subLayers here
}

Related

How to detect a transform-only UIView?

UIStackView is a transform-only view. This means it is not possible to perform operations on the view's layer.
Is there a general-case way to detect if a UIView is a transform-only view?
Actually, transform-only view is view which has CATransformLayer layer.
So the question should be How to detect a UIView which has transform-only layer? and the answer is very simple, check class's type of view.layer.
For example, when you want to change cornerRadius on stackView.layer, it will through a warning like
changing property cornerRadius in transform-only layer, will have no effect
It's not because something which is called transform-only view, it's because you are changing cornerRadius on a CATransformLayer and cornerRadius property is ignored according to Apple document for CATransformLayer.
Only the sublayers of a transform layer are rendered. The CALayer properties that are rendered by a layer are ignored, including: backgroundColor, contents, border style properties, stroke style properties, etc.
Something you maybe wrong is It's possible to perform operations on the view's layer but not all of them.
How to detect if a UIView is a transform-only view? - Use below extension
extension UIView {
func isTransformOnlyView() -> Bool {
return self.layer.isKind(of: CATransformLayer.self)
}
}
Usage
stackView.isTransformOnlyView()

When to obtain the right frame size

The problem: Let's suppose I have an image and an element which constantly triggers viewDidLayoutSubviews (a scrollview.. so that at every scroll viewDidLayout would be called... or whatever element that triggers viewDidLayout quite often).
This image as well as the "viewDidLayout_element" are all set up well with autolayout in the storyboard.
Now:
I need to make the image to be a rounded one.
with the typical: layer.cornerRadius.. and layer.masksToBounds. This requires a calculated imageView frame.
In what view controller cycle do I make it programatically? Taking into consideration that:
*except for the viewDidLayoutSubviews I don't get the right frame
*the viewDidLayoutSubviews can be called even thousands of times depending on the "viewDidLayout_element" that triggers it.
*If I call layoutIfNeeded on the imageView in viewDidAppear (because it's the only case when frames are already available so we can force their calculation) the user will already catch a glimpse of the transformation from a square image to a circular image. In other words in viewDidAppear the frame becomes available for our manipulation, but is also available for milliseconds to the user's eyes.
*it does not make sense to fill the viewDidLayoutSubviews with flags (especially if there will be something more performance intensive than a circular imageView transformation) like below:
if !iDidChangedTheImage
{
imageView.applyCornerRadius()
}
The question:
Where do I have the correct frame size inside a viewController cycle without the problems from above? Or how do you usually solve this problem?
Subclass UIImageView to your custom class, and override layoutSubviews. After that you don't need to care about image view size change and updating corner radius, just use that class for your custom image view
#implementation CustomImageView
- (void)awakeFromNib {
[super awakeFromNib];
self.layer.masksToBounds = YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.frame.size.height / 2.0;
}
#end

Swift: remove layer, without removing UIView in UITableViewCell

I have UITableViewCell with UIView in it.
I made some CABasicAnimation and attach it to new CAShapeLayer, then I add this layer to my super layer in my UITableViewCell:
self.layer.addSublayer(myLayer!)
All nice except that myLayer (and his animation) showing above my UIView.
I want that label be below UIView.
I achieve this by adding my UIView layer the same way:
self.layer.addSublayer(myViewLayer!)
In this case, my UIView layer be on the top of the CAShapeLayer with animation.
But I have a problem, I need to remove layer of UIView - myViewLayer because it violates width of the UIView when scroll.
When animation is done, and I need to remove layers, I can remove CAShapeLayer - myLayer without troubles.
myLayer?.removeFromSuperlayer()
But when I try to do the same with myViewLayer, my UIView removed too. But I don't want that, I need my UIView on screen and in my view hierarchy.
I don't understand why, if look to self.layer.sublayers I see this (before adding layers):
[<CALayer: 0x7fd1f0d4fe40>, <CALayer: 0x7fd1f0ddc950>]
And after animation done and myLayer is removed:
[<CALayer: 0x7fd1f0d4fe40>, <CALayer: 0x7fd1f0ddc950>, <CAGradientLayer: 0x7fd1f0de3660>]
As you can see CAGradientLayer is a layer of my UIView. So, I haven't it before I manually add it to sublayers array. How I can remove it, without removing my UIView?
OR how can I add myLayer below UIView?
EDIT
In few words I have this layer hierarchy before animation:
[<CALayer: 0x7fd1f0d4fe40> <- (I think this is UITableViewCell layer), <CALayer: 0x7fd1f0ddc950> <- (then, this is UITableViewCell content view layer)]
In the same time, I have this view hierarchy:
UITableViewCell -> Content View -> UIView
I need to add new layer with animation, below UIView (I want that UIView partially cover that new layer with animation). How I can do this?
I haven't my UIView layer in the UITableView layer hierarchy, so I can't just add layer with animation using addLayer:below: and so on.
I found the problem. I made a simple mistake. I used self.layer.sublayers, but I must use self.contentView.layer.sublayers this is my mistake.
When I found that, I was able to fix another issues and use addLayer:below:.
So, if you don't see a layer that must be in sublayer of your object, means that you're looking at the wrong place, think where it can be else and you'll find it, like in UITableView you need to work with contentView.
You can add myLayer below UIView.layer by setting
myLayer.zPosition = -1
The example below could have fixed your problem as well.
First solution:
self.avPlayerLayer.zPosition = 0 // or -1
self.view.layer.addSublayer(self.avPlayerLayer)
Or second solution
self.view.layer.insertSublayer(self.avPlayerLayer, below: self.button1.layer)

Programatic autolayout, constraints and UIView runtime

In reading the View Programming Guide from Apple it is apparent they have not updated it for constraints. So a couple of questions:
Many posts here talk about NOT using -initWithFrame when initializing a new UIView or setting it to CGRectZero or some equivalent. How would you handle this then?:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self){
//Set up rounded rectangle ivar
CGRect frame = [self bounds];
CGFloat radius = frame.size.height / 2;
_rectPath = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:radius];
}
}
and then putting the drawing code in the drawRect:. I have found that it won't init properly without a frame. (Obviously, I'm initing this UIView and immediately setting constraints but no frame immediately). This code set's frame = (0,0,0,0) because constraints haven't been computed yet.
If we don't put this code in init where would we put it? Also, I thought it was good practice to init all our stuff we needed in the view in init.
So this leads to question...
When is -updateConstraints called in the runtime cycle and when does the view compute it's bounds using those constraints? I have noticed that the view really doesn't know what it's bounds are until after -layoutSubviews. Is there another place before this that the UIView knows it's bounds if it has only been set with constraints?
Sorry about all the questions, but they all kinda seem connected. Thanks for the looks.
Your problem with your frames is not related to auto layout. View size changes all the time. It is almost never the same as during object instantiation. You need to create/calculate the size of paths to fit the current size during drawRect, not when you first create the view.
The only objects you should set during init are properties and/or ivars that the rest of your class relies on existing in a certain state.

Autorotation issue with UITableViewCell and CALayer

I have a UITableViewCell subclass with backgroundView set to my own UIView object. This UIView object contains three CALayer layers. I implemented - (void)layoutSubviews where I update all my CALayer layers. The problem is autorotation.
When I rotate from landscape to portrait mode there's this cosmetic issue:
During the animation, all my CALayer layers are as narrow as in portrait mode.
It seems that this guy is right:
When layoutSubviews gets called during an orientation change, the view's bounds are already set to what they will be at the conclusion of the rotation.
Source: How to achieve smooth animation when using single-step rotation / How do I get the new frame size at the start of rotation?
So, where should I update my layers to achieve proper autorotation? The view is already rotating them, so I suppose there's no need to do any custom animations, just adjust the size. Right?
Figured it out:
subclass CALayer + add sublayers into it
implement resizing of sublayers in CALayer's - (void)layoutSublayers method
subclass UIView + override + (Class)layerClass in it:
+ (Class)layerClass {
return [SubclassedCALayer class];
}
set the subclassed UIView as backgroundView of UITableViewCell

Resources