iOS Paper fold (origami / accordion) effect animation, with manual control

22,298

Solution 1

I write another library for folding transition : https://github.com/geraldhuard/YFoldView

Hope it works for you.

Solution 2

Try this. You have drag control over the paper fold, left and right side.

https://github.com/honcheng/PaperFold-for-iOS

Solution 3

This is the best solution I've seen:

http://api.mutado.com/mobile/paperstack/

EDIT: What about this: the paper folding/unfolding effect in twitter for iPad

Share:
22,298
Ashley Cameron
Author by

Ashley Cameron

Updated on July 09, 2022

Comments

  • Ashley Cameron
    Ashley Cameron almost 2 years

    I'm looking for tips on how to implement the popular 'paper folding / origami' effect in my iOS project.

    I'm aware of projects such as: https://github.com/xyfeng/XYOrigami but they only offer the 'animated' effect, with no manual control over the opening animation.

    I've struggled to dissect that project and come up with what I'm after.

    To be more exact, I'm looking on how to implement the effect shown here: http://vimeo.com/41495357 where the folding animation is not simply animated open, but the user controls the opening folds.

    Any help would be much appreciated, thanks in advance!

    EDIT:

    Okay, here's some example code to better illustrate what I'm struggling with:

    This method triggers the origami effect animation:

    - (void)showOrigamiTransitionWith:(UIView *)view 
                    NumberOfFolds:(NSInteger)folds 
                         Duration:(CGFloat)duration
                        Direction:(XYOrigamiDirection)direction
                       completion:(void (^)(BOOL finished))completion
    {
    
    if (XY_Origami_Current_State != XYOrigamiTransitionStateIdle) {
        return;
    }
    XY_Origami_Current_State = XYOrigamiTransitionStateUpdate;
    
    //add view as parent subview
    if (![view superview]) {
        [[self superview] insertSubview:view belowSubview:self];
    }
    
    
    //set frame
    CGRect selfFrame = self.frame;
    CGPoint anchorPoint;
    if (direction == XYOrigamiDirectionFromRight) {
    
        selfFrame.origin.x = self.frame.origin.x - view.bounds.size.width;
    
        view.frame = CGRectMake(self.frame.origin.x+self.frame.size.width-view.frame.size.width, self.frame.origin.y, view.frame.size.width, view.frame.size.height);
    
        anchorPoint = CGPointMake(1, 0.5);
    }
    else {
        selfFrame.origin.x = self.frame.origin.x + view.bounds.size.width;
        view.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, view.frame.size.width, view.frame.size.height);
    
        anchorPoint = CGPointMake(0, 0.5);
    }
    
    UIGraphicsBeginImageContext(view.frame.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *viewSnapShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    //set 3D depth
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1.0/800.0;
    CALayer *origamiLayer = [CALayer layer];
    origamiLayer.frame = view.bounds;
    origamiLayer.backgroundColor = [UIColor colorWithWhite:0.2 alpha:1].CGColor;
    origamiLayer.sublayerTransform = transform;
    [view.layer addSublayer:origamiLayer];
    
    
    
    
    
    
    //setup rotation angle
    double startAngle;
    CGFloat frameWidth = view.bounds.size.width;
    CGFloat frameHeight = view.bounds.size.height;
    CGFloat foldWidth = frameWidth/(folds*2);
    CALayer *prevLayer = origamiLayer;
    for (int b=0; b < folds*2; b++) {
        CGRect imageFrame;
        if (direction == XYOrigamiDirectionFromRight) {
            if(b == 0)
                startAngle = -M_PI_2;
            else {
                if (b%2)
                    startAngle = M_PI;
                else
                    startAngle = -M_PI;
            }
            imageFrame = CGRectMake(frameWidth-(b+1)*foldWidth, 0, foldWidth, frameHeight);
        }
        else {
            if(b == 0)
                startAngle = M_PI_2;
            else {
                if (b%2)
                    startAngle = -M_PI;
                else
                    startAngle = M_PI;
            }
            imageFrame = CGRectMake(b*foldWidth, 0, foldWidth, frameHeight);
        }
        CATransformLayer *transLayer = [self transformLayerFromImage:viewSnapShot Frame:imageFrame Duration:duration AnchorPiont:anchorPoint StartAngle:startAngle EndAngle:0];
        [prevLayer addSublayer:transLayer];
        prevLayer = transLayer;
    }
    
    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        self.frame = selfFrame;
        [origamiLayer removeFromSuperlayer];
        XY_Origami_Current_State = XYOrigamiTransitionStateShow;
    
        if (completion)
            completion(YES);
    }];
    
    [CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration];
    CAAnimation *openAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position.x" function:openFunction fromValue:self.frame.origin.x+self.frame.size.width/2 toValue:selfFrame.origin.x+self.frame.size.width/2];
    openAnimation.fillMode = kCAFillModeForwards;
    [openAnimation setRemovedOnCompletion:NO];
    [self.layer addAnimation:openAnimation forKey:@"position"];
    [CATransaction commit];
    }
    

    The method grabs a CATransform Layer from this method:

    - (CATransformLayer *)transformLayerFromImage:(UIImage *)image Frame:(CGRect)frame Duration:(CGFloat)duration AnchorPiont:(CGPoint)anchorPoint StartAngle:(double)start EndAngle:(double)end;
    {
    CATransformLayer *jointLayer = [CATransformLayer layer];
    jointLayer.anchorPoint = anchorPoint;
    CGFloat layerWidth;
    if (anchorPoint.x == 0) //from left to right
    {
        layerWidth = image.size.width - frame.origin.x;
        jointLayer.frame = CGRectMake(0, 0, layerWidth, frame.size.height);
        if (frame.origin.x) {
            jointLayer.position = CGPointMake(frame.size.width, frame.size.height/2);
        }
        else {
            jointLayer.position = CGPointMake(0, frame.size.height/2);
        }
    }
    else 
    { //from right to left
        layerWidth = frame.origin.x + frame.size.width;
        jointLayer.frame = CGRectMake(0, 0, layerWidth, frame.size.height);
        jointLayer.position = CGPointMake(layerWidth, frame.size.height/2);
    }
    
    //map image onto transform layer
    CALayer *imageLayer = [CALayer layer];
    imageLayer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
    imageLayer.anchorPoint = anchorPoint;
    imageLayer.position = CGPointMake(layerWidth*anchorPoint.x, frame.size.height/2);
    [jointLayer addSublayer:imageLayer];
    CGImageRef imageCrop = CGImageCreateWithImageInRect(image.CGImage, frame);
    imageLayer.contents = (__bridge id)imageCrop;
    imageLayer.backgroundColor = [UIColor clearColor].CGColor;
    
    //add shadow
    NSInteger index = frame.origin.x/frame.size.width;
    double shadowAniOpacity;
    CAGradientLayer *shadowLayer = [CAGradientLayer layer];
    shadowLayer.frame = imageLayer.bounds;
    shadowLayer.backgroundColor = [UIColor darkGrayColor].CGColor;
    shadowLayer.opacity = 0.0;
    shadowLayer.colors = [NSArray arrayWithObjects:(id)[UIColor blackColor].CGColor, (id)[UIColor clearColor].CGColor, nil];
    if (index%2) {
        shadowLayer.startPoint = CGPointMake(0, 0.5);
        shadowLayer.endPoint = CGPointMake(1, 0.5);
        shadowAniOpacity = (anchorPoint.x)?0.24:0.32;
    }
    else {
        shadowLayer.startPoint = CGPointMake(1, 0.5);
        shadowLayer.endPoint = CGPointMake(0, 0.5);
        shadowAniOpacity = (anchorPoint.x)?0.32:0.24;
    }
    [imageLayer addSublayer:shadowLayer];
    
    
    //animate open/close animation
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    [animation setDuration:duration];
    [animation setFromValue:[NSNumber numberWithDouble:start]];
    [animation setToValue:[NSNumber numberWithDouble:end]];
    [animation setRemovedOnCompletion:NO];
    [jointLayer addAnimation:animation forKey:@"jointAnimation"];
    
    
    
    //animate shadow opacity
    animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    [animation setDuration:duration];
    [animation setFromValue:[NSNumber numberWithDouble:(start)?shadowAniOpacity:0]];
    [animation setToValue:[NSNumber numberWithDouble:(start)?0:shadowAniOpacity]];
    [animation setRemovedOnCompletion:NO];
    [shadowLayer addAnimation:animation forKey:nil];
    
    return jointLayer;
    }
    

    Basically, I need to remove the automatic animation, and control the progress of the effect using some manually set value (e.g.: uislider value, or content offset).

    Once again, any help provided is much appreciated!