Custom border for UIView using CALayer and autolayout - uiview

I have to draw a custom border for a UIView.
I already have code to do that but it was written before we started using autolayout.
The code basically adds a sublayer of width/height=1.0f
-(void)BordeIzquierdo:(UIColor *)pcColor cfloatTamanio:(CGFloat )pcfloatTamanio
{
CALayer* clElemento = [self layer];
CALayer *clDerecho = [CALayer layer];
clDerecho.borderColor = [UIColor darkGrayColor].CGColor;
clDerecho.borderWidth = pcfloatTamanio;
clDerecho.frame = CGRectMake(0, 1, pcfloatTamanio, self.frame.size.height);
[clDerecho setBorderColor:pcColor.CGColor];
[clElemento addSublayer:clDerecho];
}
Now the problem is that with autolayout, this happens before layoutSubViews. So UIView.layer.frame is (0,0;0,0) and so the sublayer added is not shown because it has width/height=0.0f
Now how can I make this code without transferring this custom drawing to another method executed after viewDidLoad such as didLayoutSubviews.
I would like this styling to be correctly applied before viewDidLoad is called.
Is there anything like autolayout constraints that I can use with CALayers?
Any other ideas?

There is no CALayer autoresizing or constraints in iOS. (There is on Mac OS X, but no in iOS.)
Your requirements here are unreasonable. You say that you refuse to move the call to BordeIzquierdo:. But you can't give clDerecho a correct size until you know the size of self (and therefore self.layer). Half the art of Cocoa touch programming is putting your code in the right place. You must find the right place to put the call to BordeIzquierdo: so that the values you need have meaning at the moment it is called.
What I would do is put it in layoutSubviews along with a BOOL flag so that you only do it once.

Related

UIView's layer mask animates setting frame

I want to create a UIView subclass that masks itself so that any child view's I add to it are cropped by a circle. I want this view and it's child views to be defined in IB, so that I can easily define layout constraints to the children. So far I have the following...
#interface BubbleView ()
// eg: this is an example of a child view that would be "under" a mask
#property(weak,nonatomic) IBOutlet UIImageView *imageView;
#end
#implementation BubbleView
// not really sure if this kind of init is the right pattern, but it seems to work and
// I don't think this is my current problem??
+ (instancetype)bubbleViewFromNib {
BubbleView *view = [[NSBundle mainBundle] loadNibNamed:#"BubbleView" owner:nil options:nil][0];
UIImage *_maskingImage = [UIImage imageNamed:#"circlemask"];
CALayer *maskingLayer = [CALayer layer];
[view.layer addSublayer:maskingLayer];
maskingLayer.contents = (__bridge id _Nullable)(_maskingImage.CGImage);
view.layer.mask = maskingLayer;
return view;
}
- (void)layoutSubviews {
self.layer.mask.frame = self.bounds;
}
(note: I give the view a purple color in IB so I can see what's going on)
This almost works, but when the owning view controller resizes this view, like this...
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
BubbleView *b = (BubbleView *)[self.view viewWithTag:222];
//b.transform = CGAffineTransformScale(b.transform, 1.2, 1.2);
b.frame = CGRectInset(b.frame, -30,-30);
}
The mask does something weird: It stays small "jumps" to the upper left corner of the view, then very quickly animates to the correct size (bigger by 30,30). Why would it animate?
Before...
Fast animation like this...
Placing NSLog in layoutSubviews, I notice it gets called twice, which is strange, but still not enough times to explain the quick animation.
Also, when I change the transform instead of the frame, it resizes perfectly, with no animation. But I need to do both frame and transform changes.
Can someone tell me where I've gone wrong?
When setting an animatable property of a layer, unless that layer is a UIView's primary layer, implicit animation is the default. Moreover, the frame property is merely a facade for the bounds and position properties. For this reason, you should never set a layer's frame directly; always set the bounds and position yourself. If you don't want animation, you'll need to turn off implicit animation for these properties; the simplest way is to turn it off entirely for the current CATransaction (setDisableActions to true), but there are more subtle and precise ways to accomplish the same thing.

CALayer in awakeFromNib vs drawRect

I'm very confused, why does the following code work if I add it to awakeFromNib or initWithFrame:, but doesn't work if I add it to drawRect: or call it directly?
self.layer.cornerRadius = CGRectGetWidth(self.bounds) / 2.0f;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowRadius = 3;
self.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
self.layer.shadowOpacity = 0.75f;
For programatically created buttons, where should I add this method to? The button might be created with just init and size changed later via constraints.
Specification: By working, I mean the button will be rounded (circle if aspect ratio is 1:1) with drop shadow. By not working, I mean it'll remain a square.
Check out the detailed description in the Apple Docs, but essentially it's because you're setting your layer configurations (cornerRadius, shadow, etc.) in the middle of the draw cycle when you should have completed this before the draw cycle began.
From the drawRect: documentation:
By the time this method is called, UIKit has configured the drawing environment appropriately for your view and you can simply call whatever drawing methods and functions you need to render your content.
Other functions, like awakeFromNib: or initWithFrame: occur before the draw cycle, meaning your configurations will be taken into account before they are rendered on screen. By comparison, drawRect: assumes these basic configurations are already set and just works on rendering what you've specified on screen.

xcode/ios7: parallax effect with image clipped inside view

I am trying to implement iOS7 parallax effect. For this purpose I am using standard UIInterpolatingMotionEffect class.
Here is what I am trying to achieve:
Place photo inside UIView. Photo is larger than View's frame (on each side)
Set View's size, and CornerRadius (with masksToBounds = YES)
Move phone and watch parallax effect. Something similar as you are peaking inside hole :)
Almost every tutorial on web is simply moving whole view (by setting center.x) , but I don't know how to move content only (and clip it in same time). I have tried something, but obviously is not working:
Inside viewDidLoad I am doing next:
- (void)viewDidLoad
{
[super viewDidLoad];
_myView.layer.contents = (id) [UIImage imageNamed:#"dog"].CGImage;
_myView.layer.contentsGravity = kCAGravityCenter;
_myView.layer.masksToBounds = YES;
UIInterpolatingMotionEffect *horizontalMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"layer.position.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
horizontalMotionEffect.minimumRelativeValue = #(-80);
horizontalMotionEffect.maximumRelativeValue = #(80);
[_myView addMotionEffect:horizontalMotionEffect];
}
CALayer don't have addMotionEffect so I am accessing view.
Maybe my approach is not good from start, so if you have some other solution - it will help.
Thank you for any help.
I think a better approach here might just be to use 2 separate views.
The wrapping UIView you have now, sized to what you want with a corner radius and clipsToBounds set to YES.
a UIImageView with the image you want, added as a subview of the UIView.
Then, just apply the motion effect to the image view. This will keep the containing view static and achieve the keyhole effect you are looking for.

iOS Custom UIView Drawing - CAShapeLayers or drawRect?

I'm new to iOS UIView drawing and I'm really trying to implement a custom UIView class in a standard way.
The UIView class that I'm working on now is simple: it has mostly static background shapes that form a composite shape. I also want to add animation to this UIView class so that a simple shape can animate its path on top of the static shapes.
How I'm currently implementing this
Right now, I'm implementing the static drawing in the drawRect: method of the UIView subclass. This works fine. I also have not implemented animation of the single shape yet.
What I'm looking to have answered
Question 1:
Is it better to have the static shapes drawn in the drawRect: method as I'm currently doing it, or is there a benefit to refactoring all of the shapes being drawn into class-extension-scoped CAShapeLayer properties and doing something like:
-(instancetype) initWithFrame:(CGRect) frame
{
if (self = [super initWithFrame:frame])
{
[self setupView];
}
return self;
}
-(void) setupView // Allocate, init, and setup one-time layer properties like fill colorse.
{
self.shapeLayer1 = [CAShapeLayer layer];
[self.layer addSubLayer:shapeLayer1];
self.shapeLayer2 = [CAShapeLayer layer];
[self.layer addSubLayer:shapeLayer2];
// ... and so on
}
-(void) layoutSubviews // Set frames and paths of shape layers here.
{
self.shapeLayer1.frame = self.bounds;
self.shapeLayer2.frame = self.bounds;
self.shapeLayer1.path = [UIBezierPath somePath].CGPath;
self.shapeLayer2.path = [UIBezierPath somePath].CGPath;
}
Question 2:
Regardless of whether I implement the static shapes as CAShapeLayers or in drawRect:, is the best way to implement an animatable shape for this UIView a CAShapeLayer property that is implemented as the other CAShapeLayers would be implemented above? Creating a whole separate UIView class just for one animated shape and adding it as a subview to this view seems silly, so I'm thinking that a CAShapeLayer is the way to go to accomplish that.
I would appreciate any help with this very much!
You could do it either way, but using shape layers for everything would probably be cleaner and faster. Shape layers have built-in animation support using Core Animation.
It's typically faster to avoid implementing drawRect and instead let the system draw for you.
Edit:
I would actually word this more strongly than I did in 2014. The underlying drawing engine in iOS is optimized for tiled rendering using layers. You will get better performance and smoother animation by using CALayers and CAAnimation (or higher level UIView or SwiftUI animation which is built on CAAnimation.)

Animate frame property using CABasicAnimation

I'm trying to make an exact "translation" of this UIView block-based animation code:
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
someView.frame = CGRect(0, 100, 200, 200);
}
completion:nil];
using CABasicAnimation instead.
I'm totally aware that the frame property is actually a combination of position, bounds and anchorPoint of the underlying layer, as it is described here: http://developer.apple.com/library/mac/#qa/qa1620/_index.html
... and I already made a solution like that, using two CABasicAnimations one setting the position, one for bounds and it works for that one view.
The problem is however that I have subviews inside my view. someView has a subview of type UIScrollView in which I place still another subview of type UIImageView. UIScrollView subview has autoresizingMask set to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight. That all works perfectly if I use the UIView block-based version, however when I try using CABasicAnimations the subviews start behaving unexpectedly(i.e. get resized to incorrect widths). So it seems autoresizingMask is not working correctly when using CABasicAnimations. I noticed also that subviews don't receive a call to setFrame:, although the frame property of the parent view does change after changes to layer position and bounds are made.
That's why I would like to know what would be the correct code to replicate with CABasicAnimation that what is happening when one uses UIView's animateWithDuration method.
I'm totally aware that the frame property is actually a combination of position, bounds and anchorPoint of the underlying layer
Good, but it's important also to be aware that frame is not an animatable property for layers. If you want to animate with CABasicAnimation you must use properties that are animatable for layers. The CALayer documentation marks every such property as explicitly "animatable". The idea of using bounds and position is correct.
Thus, this code does essentially what you were doing before:
[CATransaction setDisableActions:YES];
// set final bounds and position
v.layer.bounds = CGRectMake(0,0,200,200);
v.layer.position = CGPointMake(100,200);
// cause those changes to be animated
CABasicAnimation* a1 = [CABasicAnimation animationWithKeyPath:#"bounds"];
a1.duration = 0.5;
CABasicAnimation* a2 = [CABasicAnimation animationWithKeyPath:#"position"];
a2.duration = 0.5;
[v.layer addAnimation:a1 forKey:nil];
[v.layer addAnimation:a2 forKey:nil];
However, that code has no effect on the size of any sublayers of v.layer. (A subview of v is drawn by a sublayer of v.layer.) That, unfortunately, is the problem you are trying to solve. I believe that by dropping down to the level of layers and direct explicit core animation, you have given up autoresizing, which happens at the view level. Thus you will need to animate the sublayers as well. That is what view animation was doing for you.
This is an unfortunate feature of iOS. Mac OS X has layer constraints (CAConstraint) that do at the layer level what autoresizing does at the view level (and more). But iOS is missing that feature.

Resources