I’m trying to create a UIView which shows a semitransparent circle with an opaque border inside its bounds. I want to be able to change the bounds in two ways – inside a -[UIView animateWithDuration:animations:] block and in a pinch gesture recogniser action which fires several times a second. I’ve tried three approaches based on answers elsewhere on SO, and none are suitable.
-
Setting the corner radius of the view’s layer in
layoutSubviewsgives smooth translations, but the view doesn’t stay circular during animations; it seems that cornerRadius isn’t animatable. -
Drawing the circle in
drawRect:gives a consistently circular view, but if the circle gets too big then resizing in the pinch gesture gets choppy because the device is spending too much time redrawing the circle. -
Adding a CAShapeLayer and setting its path property in
layoutSublayersOfLayer, which doesn’t animate inside UIView animations since path isn’t implicitly animatable.
Is there a way for me to create a view which is consistently circular and smoothly resizable? Is there some other type of layer I could use to take advantage of the hardware acceleration?
UPDATE
A commenter has asked me to expand on what I mean when I say that I want to change the bounds inside a -[UIView animateWithDuration:animations:] block. In my code, I have a view which contains my circle view. The circle view (the version that uses cornerRadius) overrides -[setBounds:] in order to set the corner radius:
-(void)setBounds:(CGRect)bounds
{
self.layer.cornerRadius = fminf(bounds.size.width, bounds.size.height) / 2.0;
[super setBounds:bounds];
}
The bounds of the circle view are set in -[layoutSubviews]:
-(void)layoutSubviews
{
// some other layout is performed and circleRadius and circleCenter are
// calculated based on the properties and current size of the view.
self.circleView.bounds = CGRectMake(0, 0, circleRadius*2, circleRadius*2);
self.circleView.center = circleCenter;
}
The view is sometimes resized in animations, like so:
[UIView animateWithDuration:0.33 animations:^(void) {
myView.frame = CGRectMake(x, y, w, h);
[myView setNeedsLayout];
[myView layoutIfNeeded];
}];
but during these animations, if I draw the circle view using a layer with a cornerRadius, it goes funny shapes. I can’t pass the animation duration in to layoutSubviews so I need to add the right animation within -[setBounds].
With many thanks to David, this is the solution I found. In the end what turned out to be the key to it was using the view’s
-[actionForLayer:forKey:]method, since that’s used inside UIView blocks instead of whatever the layer’s-[actionForKey]returns.By using the action that the view provides for the bounds, I was able to get the right duration, fill mode and timing function, and most importantly delegate – without that, the completion block of UIView animations didn’t run.
The radius animation follows that of the bounds in almost all circumstances – there are a few edge cases that I’m trying to iron out, but it’s basically there. It’s also worth mentioning that the pinch gestures are still sometimes jerky – I guess even the accelerated drawing is still costly.