Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you animate the sublayers of the layer of a UIView during a UIView animation?

In a UIView animation for a view, you can animate its subviews being laid out by including UIViewAnimationOptionLayoutSubviews in the options parameter of [UIView animateWithDuration:delay:options:animations:completion:]. However, I cannot find a way to animate it laying out its sublayers when they are not some view's backing layer; they would just jump into place to match the new bounds of the view. Since I'm working with layers and not views, it seems like I have to use Core Animation instead of UIView animation, but I don't know how (and when) to do this such that the layer animation would match up to the view animation.

That's my basic question. Read more if you want to know the concrete thing I'm trying to accomplish.

I've created a view with a dotted border by adding a CAShapeLayer to the view's layer (see this stackoverflow question: Dashed line border around UIView). I adjust the path of the CAShapeLayer to match the bounds of the view in layoutSubviews.

This works, but there is one cosmetic issue: when the view's bounds is animated in a UIView animation (like during rotation), the dotted border jumps to the new bounds of the view instead of smoothly animating to it as the view animates its bounds. That is, the right and bottom parts of the dotted border do not respectively stay hugged to the right and bottom parts of the view as the view animates. How can I get the dotted border from the CAShapeLayer to animate alongside the view as it animates its bounds?

What I'm doing so far is attaching a CABasicAnimation to the CAShapeLayer:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.borderLayer.path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
    self.borderLayer.frame = self.bounds;

    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    [self.borderLayer addAnimation:pathAnimation forKey:nil];
}

This does cause the dotted border to animate, but it does not have the right timing function and animation duration to match the view animation. Also, sometimes, we don't want the dotted border to animate like, when the view first does layout, the border should not animate from some old path to the new correct path; it should just appear.

like image 446
ememem Avatar asked Jul 10 '14 06:07

ememem


People also ask

Does UIView animate need weak self?

You don't need to use [weak self] in static function UIView. animate() You need to use weak when retain cycle is possible and animations block is not retained by self.

Does UIView animate run on the main thread?

The contents of your block are performed on the main thread regardless of where you call [UIView animateWithDuration:animations:] . It's best to let the OS run your animations; the animation thread does not block the main thread -- only the animation block itself.

Is UIView animate asynchronous?

UIView. animate runs on the main thread and is asynchronous.


1 Answers

You might have found the answer already, but I also faced similar problems recently, since solved it, I will post the answer.

In - layoutSubViews method, you can get current UIView animation as backing layer's CAAnimation with - animationForKey: method. Using this, You can implement - layoutSubviews like:

- (void)layoutSubviews {
    [super layoutSubviews];

    // get current animation for bounds
    CAAnimation *anim = [self.layer animationForKey:@"bounds"];

    [CATransaction begin];
    if(anim) {
        // animating, apply same duration and timing function.
        [CATransaction setAnimationDuration:anim.duration];
        [CATransaction setAnimationTimingFunction:anim.timingFunction];

        CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
        [self.borderLayer addAnimation:pathAnimation forKey:@"path"];
    }
    else {
        // not animating, we should disable implicit animations.
        [CATransaction disableActions];
    }

    self.borderLayer.path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
    self.borderLayer.frame = self.bounds;
    [CATransaction commit];
}
like image 200
rintaro Avatar answered Nov 02 '22 19:11

rintaro