Search code examples
iosanimationcalayerautolayout

Auto-Layout, animation, and layers in subview


In a constraint based view, there are several labels(top), buttons(bottom) and a customized UIView subclass (the checkers), which is consisted of many CALayers. Before animation I am trying to animate the size of the view by removing and adding constraints:

// self.containerWidth is a constraint instance 
// from Interface Builder Outlet, which is set to
// constrain the width of the whole view.
[self.calendarContainer removeConstraint:self.containerWidth];

[UIView animateWithDuration:4.0 animations:^{
    [self.calendarContainer addConstraint:
      [NSLayoutConstraint constraintWithItem:self.calendarContainer 
        attribute:NSLayoutAttributeWidth 
        relatedBy:NSLayoutRelationEqual 
        toItem:self.view 
        attribute:NSLayoutAttributeWidth
        multiplier:0
        constant:700]];
    [self.calendarContainer layoutIfNeeded];
}];

After animation, the view looks as this: After animation

During the animation, the labels(top) and buttons(bottom) were moving smoothly according to the duration given above, however, the checker view was bouncing very quickly to the final state before any other transitions occurred.

I suspect that there was something intermingled with some CATransaction implicitly.

In the checker view, I rearranged the frames of layers in -layoutSubviews:

- (void)layoutSubviews {
    [super layoutSubviews];
    if (!self.layer.sublayers.count) {
        [self prepareLayers];
    }
    int rowCount = [self rowCount];
    CGFloat rowHeight = [self rowHeight];
    [self.layer.sublayers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        CALayer *rowLayer = obj;
        NSNumber *rowIndex = [rowLayer valueForKey:@"rowindex"];
        if (rowIndex) {
            NSUInteger rowIndexValue = rowIndex.integerValue;
            if (rowIndexValue < rowCount) {
                rowLayer.hidden = NO;
                rowLayer.frame = CGRectMake(0, rowHeight * rowIndexValue, self.frame.size.width, rowHeight);
                [rowLayer.sublayers enumerateObjectsUsingBlock:^(id obj2, NSUInteger idx2, BOOL *stop2) {
                    CALayer *cellLayer = obj2;
                    NSUInteger colIndex = [[cellLayer valueForKey:@"cellindex"] integerValue] - rowIndexValue * 7 - 1;
                    cellLayer.frame = CGRectMake(colIndex*self.frame.size.width/7, 0, self.frame.size.width/7, rowHeight);
                }];
            } else {
                // hide
                rowLayer.hidden = YES;
            }
        }
    }];

}

How can I correct these animations?

BTW, where is the most recommended place to insert these layer re-positioning code?


Solution

  • It seems that the effect was resulted from an implicit animation of layers.

    The following code may fix all animation mismatches.

    NSTimeInterval duration = 4.0;
    [UIView animateWithDuration:duration animations:^{
    
        [CATransaction begin];
        [CATransaction setValue:@(duration)
                         forKey:kCATransactionAnimationDuration];
        CAMediaTimingFunction *func = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    
        [CATransaction setAnimationTimingFunction:func];
        [self.calendarContainer removeConstraint:self.containerWidth];
    
        [self.calendarContainer addConstraint:[NSLayoutConstraint constraintWithItem:self.calendarContainer attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0 constant:700]];
        [self.calendarContainer layoutIfNeeded];
        [CATransaction commit];
    }];