【问题标题】:How can i create vertical Flip View Animation in ios?如何在 ios 中创建垂直翻转视图动画?
【发布时间】:2026-01-30 16:05:01
【问题描述】:

我不知道如何垂直翻转我的uiview,我有很多视图,我每个视图

有一些照片和他们的描述,我想要一个像书一样的翻转视图,但不是

从左到右必须是从上到下或从下到上,

我想垂直翻转整个页面,例如从上到下或从下到上,

如何在 ios 中做这种工作?

我在谷歌上搜索,但那不起作用我是开发新手

所以请任何人都可以正确指导我如何翻转我的观点请帮助我

出来

提前致谢。

【问题讨论】:

标签: ios flip flipboard page-flipping


【解决方案1】:

https://github.com/mtabini/AFKPageFlipper下载代码

并更改 AFKPageFlipper.h 和 AFKPageFlipper.m 文件

//
//  AFKPageFlipper.h
//  AFKPageFlipper
//
//  Created by Marco Tabini on 10-10-11.
//  Copyright 2010 AFK Studio Partnership. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>


@class AFKPageFlipper;


@protocol AFKPageFlipperDataSource

- (NSInteger) numberOfPagesForPageFlipper:(AFKPageFlipper *) pageFlipper;
- (UIView *) viewForPage:(NSInteger) page inFlipper:(AFKPageFlipper *) pageFlipper;

@end


typedef enum {
    AFKPageFlipperDirectionTop,
    AFKPageFlipperDirectionBottom,
} AFKPageFlipperDirection;



@interface AFKPageFlipper : UIView {
    NSObject <AFKPageFlipperDataSource> *dataSource;
    NSInteger currentPage;
    NSInteger numberOfPages;

    UIView *currentView;
    UIView *nextView;

    CALayer *backgroundAnimationLayer;
    CALayer *flipAnimationLayer;

    AFKPageFlipperDirection flipDirection;
    float startFlipAngle;
    float endFlipAngle;
    float currentAngle;

    BOOL setNextViewOnCompletion;
    BOOL animating;

    BOOL disabled;
}

@property (nonatomic,retain) NSObject <AFKPageFlipperDataSource> *dataSource;
@property (nonatomic,assign) NSInteger currentPage;

@property (nonatomic, retain) UITapGestureRecognizer *tapRecognizer;
@property (nonatomic, retain) UIPanGestureRecognizer *panRecognizer;

@property (nonatomic,assign) BOOL disabled;

- (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated;

@end







//
//  AFKPageFlipper.m
//  AFKPageFlipper
//
//  Created by Marco Tabini on 10-10-12.
//  Copyright 2010 AFK Studio Partnership. All rights reserved.
//

#import "AFKPageFlipper.h"



#pragma mark -
#pragma mark UIView helpers


@interface UIView(Extended) 

- (UIImage *) imageByRenderingView;

@end


@implementation UIView(Extended)


- (UIImage *) imageByRenderingView {
    CGFloat oldAlpha = self.alpha;
    self.alpha = 1;
    UIGraphicsBeginImageContext(self.bounds.size);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    self.alpha = oldAlpha;
    return resultingImage;
}

@end


#pragma mark -
#pragma mark Private interface


@interface AFKPageFlipper()

@property (nonatomic,assign) UIView *currentView;
@property (nonatomic,assign) UIView *nextView;

@end


@implementation AFKPageFlipper

@synthesize tapRecognizer = _tapRecognizer;
@synthesize panRecognizer = _panRecognizer;


#pragma mark -
#pragma mark Flip functionality


- (void) initFlip {

    // Create screenshots of view

    UIImage *currentImage = [self.currentView imageByRenderingView];
    UIImage *newImage = [self.nextView imageByRenderingView];

    // Hide existing views

    self.currentView.alpha = 0;
    self.nextView.alpha = 0;

    // Create representational layers

    CGRect rect = self.bounds;
    rect.size.height /= 2;

    backgroundAnimationLayer = [CALayer layer];
    backgroundAnimationLayer.frame = self.bounds;
    backgroundAnimationLayer.zPosition = -300000;

    CALayer *topLayer = [CALayer layer];
    topLayer.frame = rect;
    topLayer.masksToBounds = YES;
    topLayer.contentsGravity = kCAGravityBottom;

    [backgroundAnimationLayer addSublayer:topLayer];

    rect.origin.y = rect.size.height;

    CALayer *bottomLayer = [CALayer layer];
    bottomLayer.frame = rect;
    bottomLayer.masksToBounds = YES;
    bottomLayer.contentsGravity = kCAGravityTop;

    [backgroundAnimationLayer addSublayer:bottomLayer];

    if (flipDirection == AFKPageFlipperDirectionBottom) {
        topLayer.contents = (id) [newImage CGImage];
        bottomLayer.contents = (id) [currentImage CGImage];
    } else {
        topLayer.contents = (id) [currentImage CGImage];
        bottomLayer.contents = (id) [newImage CGImage];
    }

    [self.layer addSublayer:backgroundAnimationLayer];

    rect.origin.y = 0;

    flipAnimationLayer = [CATransformLayer layer];
    flipAnimationLayer.anchorPoint = CGPointMake(0.5, 1);
    flipAnimationLayer.frame = rect;

    [self.layer addSublayer:flipAnimationLayer];

    CALayer *backLayer = [CALayer layer];
    backLayer.frame = flipAnimationLayer.bounds;
    backLayer.doubleSided = NO;
    backLayer.masksToBounds = YES;

    [flipAnimationLayer addSublayer:backLayer];

    CALayer *frontLayer = [CALayer layer];
    frontLayer.frame = flipAnimationLayer.bounds;
    frontLayer.doubleSided = NO;
    frontLayer.masksToBounds = YES;

    frontLayer.transform = CATransform3DMakeRotation(M_PI, 1.0, 0.0, 0);

    [flipAnimationLayer addSublayer:frontLayer];

    if (flipDirection == AFKPageFlipperDirectionBottom) {
        backLayer.contents = (id) [currentImage CGImage];
        backLayer.contentsGravity = kCAGravityBottom;

        frontLayer.contents = (id) [newImage CGImage];
        frontLayer.contentsGravity = kCAGravityTop;

        CATransform3D transform = CATransform3DMakeRotation(1.1/M_PI, 1.0, 0.0, 0.0);
        transform.m34 = 1.0f / 2500.0f;

        flipAnimationLayer.transform = transform;

        currentAngle = startFlipAngle = 0;
        endFlipAngle = M_PI;
    } else {
        //down
        backLayer.contents = (id) [newImage CGImage];
        backLayer.contentsGravity = kCAGravityBottom;

        frontLayer.contents = (id) [currentImage CGImage];
        frontLayer.contentsGravity = kCAGravityTop;

        CATransform3D transform = CATransform3DMakeRotation(M_PI/1.1, 1.0, 0.0, 0.0);
        transform.m34 = 1.0f / 2500.0f;

        flipAnimationLayer.transform = transform;

        currentAngle = startFlipAngle = M_PI;
        endFlipAngle = 0;
    }
}


- (void) cleanupFlip {
    [backgroundAnimationLayer removeFromSuperlayer];
    [flipAnimationLayer removeFromSuperlayer];

    backgroundAnimationLayer = Nil;
    flipAnimationLayer = Nil;

    animating = NO;

    if (setNextViewOnCompletion) {
        [self.currentView removeFromSuperview];
        self.currentView = self.nextView;
        self.nextView = Nil;
    } else {
        [self.nextView removeFromSuperview];
        self.nextView = Nil;
    }

    self.currentView.alpha = 1;
}


- (void) setFlipProgress:(float) progress setDelegate:(BOOL) setDelegate animate:(BOOL) animate {
    if (animate) {
        animating = YES;
    }

    float newAngle = startFlipAngle + progress * (endFlipAngle - startFlipAngle);

    float duration = animate ? 0.5 * fabs((newAngle - currentAngle) / (endFlipAngle - startFlipAngle)) : 0;

    currentAngle = newAngle;

    CATransform3D endTransform = CATransform3DIdentity;
    endTransform.m34 = 1.0f / 2500.0f;
    endTransform = CATransform3DRotate(endTransform, newAngle, 1.0, 0.0, 0.0);

    [flipAnimationLayer removeAllAnimations];

    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];

    flipAnimationLayer.transform = endTransform;

    [CATransaction commit];

    if (setDelegate) {
        [self performSelector:@selector(cleanupFlip) withObject:Nil afterDelay:duration];
    }
}


- (void) flipPage {
    [self setFlipProgress:1.0 setDelegate:YES animate:YES];
}


#pragma mark -
#pragma mark Animation management


- (void)animationDidStop:(NSString *) animationID finished:(NSNumber *) finished context:(void *) context {
    [self cleanupFlip];
}


#pragma mark -
#pragma mark Properties

@synthesize currentView;


- (void) setCurrentView:(UIView *) value {
    if (currentView) {
        [currentView release];
    }

    currentView = [value retain];
}


@synthesize nextView;


- (void) setNextView:(UIView *) value {
    if (nextView) {
        [nextView release];
    }

    nextView = [value retain];
}


@synthesize currentPage;


- (BOOL) doSetCurrentPage:(NSInteger) value {
    if (value == currentPage) {
        return FALSE;
    }

    flipDirection = value < currentPage ? AFKPageFlipperDirectionBottom : AFKPageFlipperDirectionTop;

    currentPage = value;

    self.nextView = [self.dataSource viewForPage:value inFlipper:self];
    [self addSubview:self.nextView];

    return TRUE;
}   

- (void) setCurrentPage:(NSInteger) value {
    if (![self doSetCurrentPage:value]) {
        return;
    }

    setNextViewOnCompletion = YES;
    animating = YES;

    self.nextView.alpha = 0;

    [UIView beginAnimations:@"" context:Nil];
    [UIView setAnimationDuration:0.5];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

    self.nextView.alpha = 1;

    [UIView commitAnimations];
} 


- (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated {
    if (![self doSetCurrentPage:value]) {
        return;
    }

    setNextViewOnCompletion = YES;
    animating = YES;

    if (animated) {
        [self initFlip];
        [self performSelector:@selector(flipPage) withObject:Nil afterDelay:0.091];
    } else {
        [self animationDidStop:Nil finished:[NSNumber numberWithBool:NO] context:Nil];
    }

}


@synthesize dataSource;


- (void) setDataSource:(NSObject <AFKPageFlipperDataSource>*) value {
    if (dataSource) {
        [dataSource release];
    }

    dataSource = [value retain];
    numberOfPages = [dataSource numberOfPagesForPageFlipper:self];
    currentPage = 0;
    self.currentPage = 1;
}


@synthesize disabled;


- (void) setDisabled:(BOOL) value {
    disabled = value;

    self.userInteractionEnabled = !value;

    for (UIGestureRecognizer *recognizer in self.gestureRecognizers) {
        recognizer.enabled = !value;
    }
}


#pragma mark -
#pragma mark Touch management


- (void) tapped:(UITapGestureRecognizer *) recognizer {
    if (animating || self.disabled) {
        return;
    }

    if (recognizer.state == UIGestureRecognizerStateRecognized) {
        NSInteger newPage;

        if ([recognizer locationInView:self].y < (self.bounds.size.height - self.bounds.origin.y) / 2) {
            newPage = MAX(1, self.currentPage - 1);
        } else {
            newPage = MIN(self.currentPage + 1, numberOfPages);
        }

        [self setCurrentPage:newPage animated:YES];
    }
}


- (void) panned:(UIPanGestureRecognizer *) recognizer {
    if (animating) {
        return;
    }

    static BOOL hasFailed;
    static BOOL initialized;

    static NSInteger oldPage;



    float translation = [recognizer translationInView:self].y;


    float progress = translation / self.bounds.size.height;


    if (flipDirection == AFKPageFlipperDirectionTop) {
        progress = MIN(progress, 0);
    } else {
        progress = MAX(progress, 0);
    }

    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
            hasFailed = FALSE;
            initialized = FALSE;
            animating = NO;
            setNextViewOnCompletion = NO;
            break;


        case UIGestureRecognizerStateChanged:

            if (hasFailed) {
                return;
            }

            if (!initialized) {
                oldPage = self.currentPage;

                if (translation > 0) {
                    if (self.currentPage > 1) {
                        [self doSetCurrentPage:self.currentPage - 1];
                    } else {
                        hasFailed = TRUE;
                        return;
                    }
                } else {
                    if (self.currentPage < numberOfPages) {
                        [self doSetCurrentPage:self.currentPage + 1];
                    } else {
                        hasFailed = TRUE;
                        return;
                    }
                }

                hasFailed = NO;
                initialized = TRUE;
                setNextViewOnCompletion = NO;

                [self initFlip];
            }

            [self setFlipProgress:fabs(progress) setDelegate:NO animate:NO];

            break;


        case UIGestureRecognizerStateFailed:
            [self setFlipProgress:0.0 setDelegate:YES animate:YES];
            currentPage = oldPage;
            break;

        case UIGestureRecognizerStateRecognized:
            if (hasFailed) {
                [self setFlipProgress:0.0 setDelegate:YES animate:YES];
                currentPage = oldPage;

                return;
            }

            if (fabs((translation + [recognizer velocityInView:self].y / 4) / self.bounds.size.height) > 0.5) {
                setNextViewOnCompletion = YES;
                [self setFlipProgress:1.0 setDelegate:YES animate:YES];
            } else {
                [self setFlipProgress:0.0 setDelegate:YES animate:YES];
                currentPage = oldPage;
            }

            break;
        default:
            break;
    }
}


#pragma mark -
#pragma mark Frame management


- (void) setFrame:(CGRect) value {
    super.frame = value;

    numberOfPages = [dataSource numberOfPagesForPageFlipper:self];

    if (self.currentPage > numberOfPages) {
        self.currentPage = numberOfPages;
    }

}


#pragma mark -
#pragma mark Initialization and memory management


+ (Class) layerClass {
    return [CATransformLayer class];
}

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
        _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)];

        [_tapRecognizer requireGestureRecognizerToFail:_panRecognizer];

        [self addGestureRecognizer:_tapRecognizer];
        [self addGestureRecognizer:_panRecognizer];
    }
    return self;
}


- (void)dealloc {
    self.dataSource = Nil;
    self.currentView = Nil;
    self.nextView = Nil;
    self.tapRecognizer = Nil;
    self.panRecognizer = Nil;
    [super dealloc];
}


@end

因为 AFKPageFlipper,我能够执行上述代码....感谢 mtabini 先生(AFKPageFlipper 作者)

【讨论】:

    【解决方案2】:

    你可以设置一个负数,比如:

    [theView setTransform:CGAffineTransformMakeScale(1, -1)];
    

    【讨论】:

    【解决方案3】:

    与 Pradeep 相同,但有阴影,也适用于 ARC:

    AFKPageFlipper.h

    //
    //  AFKPageFlipper.h
    //  AFKPageFlipper
    //
    //  Created by Marco Tabini on 10-10-11.
    //  Copyright 2010 AFK Studio Partnership. All rights reserved.
    //
    
    #import <UIKit/UIKit.h>
    #import <QuartzCore/QuartzCore.h>
    
    
    @class AFKPageFlipper;
    
    
    @protocol AFKPageFlipperDataSource
    
    - (NSInteger) numberOfPagesForPageFlipper:(AFKPageFlipper *) pageFlipper;
    - (UIView *) viewForPage:(NSInteger) page inFlipper:(AFKPageFlipper *) pageFlipper;
    
    @end
    
    
    typedef enum {
        AFKPageFlipperDirectionTop,
        AFKPageFlipperDirectionBottom,
    } AFKPageFlipperDirection;
    
    
    
    @interface AFKPageFlipper : UIView {
        NSObject <AFKPageFlipperDataSource> *dataSource;
        NSInteger currentPage;
        NSInteger numberOfPages;
    
        // shadows
        CALayer *frontLayerShadow;
        CALayer *backLayerShadow;
        CALayer *leftLayerShadow;
        CALayer *rightLayerShadow;
        // shadows
    
        CALayer *backgroundAnimationLayer;
        CALayer *flipAnimationLayer;
    
    
    
        AFKPageFlipperDirection flipDirection;
        float startFlipAngle;
        float endFlipAngle;
        float currentAngle;
    
        BOOL setNextViewOnCompletion;
        BOOL animating;
    
        BOOL disabled;
    }
    
    @property (nonatomic,retain) NSObject <AFKPageFlipperDataSource> *dataSource;
    @property (nonatomic,assign) NSInteger currentPage;
    
    @property (nonatomic, retain) UITapGestureRecognizer *tapRecognizer;
    @property (nonatomic, retain) UIPanGestureRecognizer *panRecognizer;
    
    @property (nonatomic,assign) BOOL disabled;
    
    - (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated;
    
    @end
    

    AFKPageFlipper.m

    //
    //  AFKPageFlipper.m
    //  AFKPageFlipper
    //
    //  Created by Marco Tabini on 10-10-12.
    //  Copyright 2010 AFK Studio Partnership. All rights reserved.
    //
    
    #import "AFKPageFlipper.h"
    
    
    
    #pragma mark -
    #pragma mark UIView helpers
    
    
    @interface UIView(Extended)
    
    - (UIImage *) imageByRenderingView;
    
    @end
    
    
    @implementation UIView(Extended)
    
    
    - (UIImage *) imageByRenderingView {
        CGFloat oldAlpha = self.alpha;
        self.alpha = 1;
        UIGraphicsBeginImageContext(self.bounds.size);
        [self.layer renderInContext:UIGraphicsGetCurrentContext()];
        UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        self.alpha = oldAlpha;
        return resultingImage;
    }
    
    @end
    
    
    #pragma mark -
    #pragma mark Private interface
    
    
    @interface AFKPageFlipper()
    
    @property (nonatomic,retain) UIView *currentView;
    @property (nonatomic,retain) UIView *nextView;
    
    @end
    
    
    @implementation AFKPageFlipper
    
    @synthesize tapRecognizer = _tapRecognizer;
    @synthesize panRecognizer = _panRecognizer;
    
    
    #pragma mark -
    #pragma mark Flip functionality
    
    
    - (void) initFlip {
    
        // Create screenshots of view
    
        UIImage *currentImage = [self.currentView imageByRenderingView];
        UIImage *newImage = [self.nextView imageByRenderingView];
    
        // Hide existing views
    
        self.currentView.alpha = 0;
        self.nextView.alpha = 0;
    
        // Create representational layers
    
        CGRect rect = self.bounds;
        rect.size.height /= 2;
    
        backgroundAnimationLayer = [CALayer layer];
        backgroundAnimationLayer.frame = self.bounds;
        backgroundAnimationLayer.zPosition = -300000;
    
        CALayer *topLayer = [CALayer layer];
        topLayer.frame = rect;
        topLayer.masksToBounds = YES;
        topLayer.contentsGravity = kCAGravityBottom;
    
        [backgroundAnimationLayer addSublayer:topLayer];
    
        rect.origin.y = rect.size.height;
    
        CALayer *bottomLayer = [CALayer layer];
        bottomLayer.frame = rect;
        bottomLayer.masksToBounds = YES;
        bottomLayer.contentsGravity = kCAGravityTop;
    
        [backgroundAnimationLayer addSublayer:bottomLayer];
    
        if (flipDirection == AFKPageFlipperDirectionBottom) {
            topLayer.contents = (id) [newImage CGImage];
            bottomLayer.contents = (id) [currentImage CGImage];
        } else {
            topLayer.contents = (id) [currentImage CGImage];
            bottomLayer.contents = (id) [newImage CGImage];
        }
    
        [self.layer addSublayer:backgroundAnimationLayer];
    
        rect.origin.y = 0;
    
        flipAnimationLayer = [CATransformLayer layer];
        flipAnimationLayer.anchorPoint = CGPointMake(0.5, 1);
        flipAnimationLayer.frame = rect;
    
        [self.layer addSublayer:flipAnimationLayer];
    
        CALayer *backLayer = [CALayer layer];
        backLayer.frame = flipAnimationLayer.bounds;
        backLayer.doubleSided = NO;
        backLayer.masksToBounds = YES;
    
        [flipAnimationLayer addSublayer:backLayer];
    
        CALayer *frontLayer = [CALayer layer];
        frontLayer.frame = flipAnimationLayer.bounds;
        frontLayer.doubleSided = NO;
        frontLayer.masksToBounds = YES;
    
        frontLayer.transform = CATransform3DMakeRotation(M_PI, 1.0, 0, 0);
    
        [flipAnimationLayer addSublayer:frontLayer];
    
        // shadows
        frontLayerShadow = [CALayer layer];
        frontLayerShadow.frame = frontLayer.bounds;
        frontLayerShadow.doubleSided = NO;
        frontLayerShadow.masksToBounds = YES;
        frontLayerShadow.opacity = 0;
        frontLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
        [frontLayer addSublayer:frontLayerShadow];
    
        backLayerShadow = [CALayer layer];
        backLayerShadow.frame = backLayer.bounds;
        backLayerShadow.doubleSided = NO;
        backLayerShadow.masksToBounds = YES;
        backLayerShadow.opacity = 0;
        backLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
        [backLayer addSublayer:backLayerShadow];
    
    
        leftLayerShadow = [CALayer layer];
        leftLayerShadow.frame = topLayer.bounds;
        leftLayerShadow.doubleSided = NO;
        leftLayerShadow.masksToBounds = YES;
        leftLayerShadow.opacity = 0.0;
        leftLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
        [topLayer addSublayer:leftLayerShadow];
    
        rightLayerShadow = [CALayer layer];
        rightLayerShadow.frame = bottomLayer.bounds;
        rightLayerShadow.doubleSided = NO;
        rightLayerShadow.masksToBounds = YES;
        rightLayerShadow.opacity = 0.0;
        rightLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
        [bottomLayer addSublayer:rightLayerShadow];
        // shadows
    
    
        if (flipDirection == AFKPageFlipperDirectionBottom) {
            backLayer.contents = (id) [currentImage CGImage];
            backLayer.contentsGravity = kCAGravityBottom;
    
            frontLayer.contents = (id) [newImage CGImage];
            frontLayer.contentsGravity = kCAGravityTop;
    
            CATransform3D transform = CATransform3DMakeRotation(1.1/M_PI, 1.0, 0.0, 0.0);
            transform.m34 = 1.0f / 2500.0f;
    
            flipAnimationLayer.transform = transform;
    
            currentAngle = startFlipAngle = 0;
            endFlipAngle = M_PI;
        } else {
            //down
            backLayer.contents = (id) [newImage CGImage];
            backLayer.contentsGravity = kCAGravityBottom;
    
            frontLayer.contents = (id) [currentImage CGImage];
            frontLayer.contentsGravity = kCAGravityTop;
    
            CATransform3D transform = CATransform3DMakeRotation(M_PI/1.1, 1.0, 0.0, 0.0);
            transform.m34 = 1.0f / 2500.0f;
    
            flipAnimationLayer.transform = transform;
    
            currentAngle = startFlipAngle = M_PI;
            endFlipAngle = 0;
        }
    }
    
    
    - (void) cleanupFlip {
        [backgroundAnimationLayer removeFromSuperlayer];
        [flipAnimationLayer removeFromSuperlayer];
    
        backgroundAnimationLayer = Nil;
        flipAnimationLayer = Nil;
    
        animating = NO;
    
        if (setNextViewOnCompletion) {
            [self.currentView removeFromSuperview];
            self.currentView = self.nextView;
            self.nextView = Nil;
        } else {
            [self.nextView removeFromSuperview];
            self.nextView = Nil;
        }
    
        self.currentView.alpha = 1;
    }
    
    
    - (void) setFlipProgress:(float) progress setDelegate:(BOOL) setDelegate animate:(BOOL) animate {
        if (animate) {
            animating = YES;
        }
    
        float newAngle = startFlipAngle + progress * (endFlipAngle - startFlipAngle);
    
        float duration = animate ? 0.5 * fabs((newAngle - currentAngle) / (endFlipAngle - startFlipAngle)) : 0;
    
        currentAngle = newAngle;
    
        CATransform3D endTransform = CATransform3DIdentity;
        endTransform.m34 = 1.0f / 2500.0f;
        endTransform = CATransform3DRotate(endTransform, newAngle, 1.0, 0.0, 0.0);
    
        [flipAnimationLayer removeAllAnimations];
    
        // shadows
        //NSLog(@"End flip angle: %.0f, \tstartflip: %.0f, \tprogress: %.2f\tduration: %.2f", endFlipAngle, startFlipAngle,progress, duration);
        CGFloat newShadowOpacity = (0.5 - progress);
    
        if(newShadowOpacity < 0) {
            newShadowOpacity *= -1;
        }
        if (newShadowOpacity < 0.05) {
            newShadowOpacity = 0;
        }
    
        // shadows
    
        if (duration < 0.15) {
            duration = 0.15;
        }
    
        [UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveLinear animations: ^(void) {
            flipAnimationLayer.transform = endTransform;
    
    
            if (endFlipAngle < startFlipAngle) {
    
                if(progress < 0.5) {
                    rightLayerShadow.opacity = newShadowOpacity;
                    frontLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
    
                } else {
                    backLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
                    leftLayerShadow.opacity = newShadowOpacity;
                }
    
            } else {
                if(progress < 0.5) {
                    leftLayerShadow.opacity = newShadowOpacity;
                    backLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
    
    
                } else {
                    frontLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
                    rightLayerShadow.opacity = newShadowOpacity;
                }
            }
    
            // shadows
        } completion: ^(BOOL completion) {
    
        }];
    
    
        if (setDelegate) {
            [self performSelector:@selector(cleanupFlip) withObject:Nil afterDelay:duration];
        }
    }
    
    
    - (void) flipPage {
        [self setFlipProgress:1.0 setDelegate:YES animate:YES];
    }
    
    
    #pragma mark -
    #pragma mark Animation management
    
    
    - (void)animationDidStop:(NSString *) animationID finished:(NSNumber *) finished context:(void *) context {
        [self cleanupFlip];
    }
    
    
    #pragma mark -
    #pragma mark Properties
    
    
    @synthesize currentPage;
    
    
    - (BOOL) doSetCurrentPage:(NSInteger) value {
        if (value == currentPage) {
            return FALSE;
        }
    
        flipDirection = value < currentPage ? AFKPageFlipperDirectionBottom : AFKPageFlipperDirectionTop;
    
        currentPage = value;
    
        self.nextView = [self.dataSource viewForPage:value inFlipper:self];
        [self addSubview:self.nextView];
    
        return TRUE;
    }
    
    - (void) setCurrentPage:(NSInteger) value {
        if (![self doSetCurrentPage:value]) {
            return;
        }
    
        setNextViewOnCompletion = YES;
        animating = YES;
    
        self.nextView.alpha = 0;
    
        [UIView beginAnimations:@"" context:Nil];
        [UIView setAnimationDuration:0.5];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
    
        self.nextView.alpha = 1;
    
        [UIView commitAnimations];
    }
    
    
    - (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated {
        if (![self doSetCurrentPage:value]) {
            return;
        }
    
        setNextViewOnCompletion = YES;
        animating = YES;
    
        if (animated) {
            [self initFlip];
            [self performSelector:@selector(flipPage) withObject:Nil afterDelay:0.091];
        } else {
            [self animationDidStop:Nil finished:[NSNumber numberWithBool:NO] context:Nil];
        }
    
    }
    
    
    @synthesize dataSource;
    
    
    - (void) setDataSource:(NSObject <AFKPageFlipperDataSource>*) value {
        if (dataSource) {
            dataSource = nil;
        }
    
        dataSource = value;
        numberOfPages = [dataSource numberOfPagesForPageFlipper:self];
        currentPage = 0;
        self.currentPage = 1;
    }
    
    
    @synthesize disabled;
    
    
    - (void) setDisabled:(BOOL) value {
        disabled = value;
    
        self.userInteractionEnabled = !value;
    
        for (UIGestureRecognizer *recognizer in self.gestureRecognizers) {
            recognizer.enabled = !value;
        }
    }
    
    
    #pragma mark -
    #pragma mark Touch management
    
    
    - (void) tapped:(UITapGestureRecognizer *) recognizer {
        if (animating || self.disabled) {
            return;
        }
    
        if (recognizer.state == UIGestureRecognizerStateRecognized) {
            NSInteger newPage;
    
            if ([recognizer locationInView:self].y < (self.bounds.size.height - self.bounds.origin.y) / 2) {
                newPage = MAX(1, self.currentPage - 1);
            } else {
                newPage = MIN(self.currentPage + 1, numberOfPages);
            }
    
            [self setCurrentPage:newPage animated:YES];
        }
    }
    
    
    - (void) panned:(UIPanGestureRecognizer *) recognizer {
        if (animating) {
            return;
        }
    
        static BOOL hasFailed;
        static BOOL initialized;
    
        static NSInteger oldPage;
    
    
    
        float translation = [recognizer translationInView:self].y;
    
    
        float progress = translation / self.bounds.size.height;
    
    
        if (flipDirection == AFKPageFlipperDirectionTop) {
            progress = MIN(progress, 0);
        } else {
            progress = MAX(progress, 0);
        }
    
        switch (recognizer.state) {
            case UIGestureRecognizerStateBegan:
            hasFailed = FALSE;
            initialized = FALSE;
            animating = NO;
            setNextViewOnCompletion = NO;
            break;
    
    
            case UIGestureRecognizerStateChanged:
    
            if (hasFailed) {
                return;
            }
    
            if (!initialized) {
                oldPage = self.currentPage;
    
                if (translation > 0) {
                    if (self.currentPage > 1) {
                        [self doSetCurrentPage:self.currentPage - 1];
                    } else {
                        hasFailed = TRUE;
                        return;
                    }
                } else {
                    if (self.currentPage < numberOfPages) {
                        [self doSetCurrentPage:self.currentPage + 1];
                    } else {
                        hasFailed = TRUE;
                        return;
                    }
                }
    
                hasFailed = NO;
                initialized = TRUE;
                setNextViewOnCompletion = NO;
    
                [self initFlip];
            }
    
            [self setFlipProgress:fabs(progress) setDelegate:NO animate:NO];
    
            break;
    
    
            case UIGestureRecognizerStateFailed:
            [self setFlipProgress:0.0 setDelegate:YES animate:YES];
            currentPage = oldPage;
            break;
    
            case UIGestureRecognizerStateRecognized:
            if (hasFailed) {
                [self setFlipProgress:0.0 setDelegate:YES animate:YES];
                currentPage = oldPage;
    
                return;
            }
    
            if (fabs((translation + [recognizer velocityInView:self].y / 4) / self.bounds.size.height) > 0.5) {
                setNextViewOnCompletion = YES;
                [self setFlipProgress:1.0 setDelegate:YES animate:YES];
            } else {
                [self setFlipProgress:0.0 setDelegate:YES animate:YES];
                currentPage = oldPage;
            }
    
            break;
            default:
            break;
        }
    }
    
    
    #pragma mark -
    #pragma mark Frame management
    
    /*
    - (void) setFrame:(CGRect) value {
        super.frame = value;
    
        numberOfPages = [dataSource numberOfPagesForPageFlipper:self];
    
        if (self.currentPage > numberOfPages) {
            self.currentPage = numberOfPages;
        }
    
    }*/
    
    
    #pragma mark -
    #pragma mark Initialization and memory management
    
    
    + (Class) layerClass {
        return [CATransformLayer class];
    }
    
    - (id)initWithFrame:(CGRect)frame {
        if ((self = [super initWithFrame:frame])) {
            [self initRecognizers];
        }
        return self;
    }
    
    - (id)initWithCoder:(NSCoder *)aDecoder {
        if ((self = [super initWithCoder: aDecoder])) {
            [self initRecognizers];
        }
        return self;
    }
    
    - (void) initRecognizers {
        _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
        _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)];
    
        [_tapRecognizer requireGestureRecognizerToFail:_panRecognizer];
    
        [self addGestureRecognizer:_tapRecognizer];
        [self addGestureRecognizer:_panRecognizer];
    }
    
    
    @end
    

    【讨论】:

    • 我在第一次从上到下运行项目时遇到了问题。现在我从下到上平移它没有翻转......