├── .gitignore ├── Container ├── MPAnimation.h ├── MPAnimation.m ├── MPFlipEnumerations.h ├── MPFlipTransition.h ├── MPFlipTransition.m ├── MPFlipViewController.h ├── MPFlipViewController.m ├── MPTransition.h ├── MPTransition.m └── MPTransitionEnumerations.h ├── Default-568h@2x.png ├── Demo ├── AppDelegate.h ├── AppDelegate.m ├── ContentViewController.h ├── ContentViewController.m ├── MPFlipViewController-Info.plist ├── MPFlipViewController-Prefix.pch ├── Pattern - Aged Paper.png ├── Pattern - Aged Paper@2x.png ├── Pattern - Apple Wood.png ├── Pattern - Apple Wood@2x.png ├── Pattern - Corkboard.png ├── Pattern - Corkboard@2x.png ├── ViewController.h ├── ViewController.m ├── en.lproj │ ├── InfoPlist.strings │ ├── MainStoryboard_iPad.storyboard │ └── MainStoryboard_iPhone.storyboard ├── main.m ├── matrix_01.png ├── matrix_01@2x.png ├── matrix_02.png ├── matrix_02@2x.png ├── matrix_03.png └── matrix_03@2x.png ├── Icon-72.png ├── Icon-72@2x.png ├── Icon.png ├── Icon@2x.png ├── MPFlipViewController.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── README.md └── Source Code License.rtf /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | MPFlipViewController.xcodeproj/xcuserdata/mark.xcuserdatad/xcschemes/xcschememanagement.plist 5 | 6 | MPFlipViewController.xcodeproj/xcuserdata/mark.xcuserdatad/xcschemes/MPFlipViewController.xcscheme 7 | 8 | MPFlipViewController.xcodeproj/project.xcworkspace/xcuserdata/mark.xcuserdatad/WorkspaceSettings.xcsettings 9 | 10 | MPFlipViewController.xcodeproj/project.xcworkspace/xcuserdata/mark.xcuserdatad/UserInterfaceState.xcuserstate 11 | 12 | MPFlipViewController.xcodeproj/xcuserdata/mark.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist 13 | -------------------------------------------------------------------------------- /Container/MPAnimation.h: -------------------------------------------------------------------------------- 1 | // 2 | // MPAnimation.h 3 | // EnterTheMatrix 4 | // 5 | // Created by Mark Pospesel on 3/10/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MPAnimation : NSObject 12 | 13 | + (UIImage *)renderImageFromView:(UIView *)view; 14 | + (UIImage *)renderImageFromView:(UIView *)view withRect:(CGRect)frame; 15 | + (UIImage *)renderImageFromView:(UIView *)view withRect:(CGRect)frame transparentInsets:(UIEdgeInsets)insets; 16 | + (UIImage *)renderImageForAntialiasing:(UIImage *)image withInsets:(UIEdgeInsets)insets; 17 | + (UIImage *)renderImageForAntialiasing:(UIImage *)image; 18 | + (UIImage *)renderImage:(UIImage *)image withMargin:(CGFloat)width color:(UIColor *)color; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Container/MPAnimation.m: -------------------------------------------------------------------------------- 1 | // 2 | // MPAnimation.m 3 | // EnterTheMatrix 4 | // 5 | // Created by Mark Pospesel on 3/10/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import "MPAnimation.h" 10 | #import 11 | 12 | @implementation MPAnimation 13 | 14 | // Generates an image from the view (view must be opaque) 15 | + (UIImage *)renderImageFromView:(UIView *)view 16 | { 17 | return [self renderImageFromView:view withRect:view.bounds]; 18 | } 19 | 20 | // Generates an image from the (opaque) view where frame is a rectangle in the view's coordinate space. 21 | // Pass in bounds to render the entire view, or another rect to render a subset of the view 22 | + (UIImage *)renderImageFromView:(UIView *)view withRect:(CGRect)frame 23 | { 24 | // Create a new context of the desired size to render the image 25 | UIGraphicsBeginImageContextWithOptions(frame.size, YES, 0); 26 | CGContextRef context = UIGraphicsGetCurrentContext(); 27 | 28 | // Translate it, to the desired position 29 | CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y); 30 | 31 | // Render the view as image 32 | [view.layer renderInContext:context]; 33 | 34 | // Fetch the image 35 | UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext(); 36 | 37 | // Cleanup 38 | UIGraphicsEndImageContext(); 39 | 40 | return renderedImage; 41 | } 42 | 43 | // Generates an image from the view with transparent margins. 44 | // (CGRect)frame is a rectangle in the view's coordinate space- pass in bounds to render the entire view, or another rect to render a subset of the view 45 | // (UIEdgeInsets)insets defines the size of the transparent margins to create 46 | + (UIImage *)renderImageFromView:(UIView *)view withRect:(CGRect)frame transparentInsets:(UIEdgeInsets)insets 47 | { 48 | CGSize imageSizeWithBorder = CGSizeMake(frame.size.width + insets.left + insets.right, frame.size.height + insets.top + insets.bottom); 49 | // Create a new context of the desired size to render the image 50 | UIGraphicsBeginImageContextWithOptions(imageSizeWithBorder, UIEdgeInsetsEqualToEdgeInsets(insets, UIEdgeInsetsZero), 0); 51 | CGContextRef context = UIGraphicsGetCurrentContext(); 52 | 53 | // Clip the context to the portion of the view we will draw 54 | CGContextClipToRect(context, (CGRect){{insets.left, insets.top}, frame.size}); 55 | // Translate it, to the desired position 56 | CGContextTranslateCTM(context, -frame.origin.x + insets.left, -frame.origin.y + insets.top); 57 | 58 | // Render the view as image 59 | [view.layer renderInContext:context]; 60 | 61 | // Fetch the image 62 | UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext(); 63 | 64 | // Cleanup 65 | UIGraphicsEndImageContext(); 66 | 67 | return renderedImage; 68 | } 69 | 70 | // Generates a copy of the image with a 1 point transparent margin around it 71 | + (UIImage *)renderImageForAntialiasing:(UIImage *)image 72 | { 73 | return [self renderImageForAntialiasing:image withInsets:UIEdgeInsetsMake(1, 1, 1, 1)]; 74 | } 75 | 76 | // Generates a copy of the image with transparent margins of size defined by the insets parameter 77 | + (UIImage *)renderImageForAntialiasing:(UIImage *)image withInsets:(UIEdgeInsets)insets 78 | { 79 | CGSize imageSizeWithBorder = CGSizeMake([image size].width + insets.left + insets.right, [image size].height + insets.top + insets.bottom); 80 | 81 | // Create a new context of the desired size to render the image 82 | UIGraphicsBeginImageContextWithOptions(imageSizeWithBorder, UIEdgeInsetsEqualToEdgeInsets(insets, UIEdgeInsetsZero), 0); 83 | 84 | // The image starts off filled with clear pixels, so we don't need to explicitly fill them here 85 | [image drawInRect:(CGRect){{insets.left, insets.top}, [image size]}]; 86 | 87 | // Fetch the image 88 | UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext(); 89 | 90 | UIGraphicsEndImageContext(); 91 | 92 | return renderedImage; 93 | } 94 | 95 | + (UIImage *)renderImage:(UIImage *)image withMargin:(CGFloat)width color:(UIColor *)color 96 | { 97 | CGSize imageSizeWithBorder = CGSizeMake([image size].width + 2 * (width + 1), [image size].height + 2 * (width + 1)); 98 | 99 | // Create a new context of the desired size to render the image 100 | UIGraphicsBeginImageContextWithOptions(imageSizeWithBorder, NO, 0); 101 | 102 | // The image starts off filled with clear pixels, so we don't need to explicitly fill them here. 103 | CGRect rect = CGRectMake(1, 1, [image size].width + 2 * width, [image size].height + 2 * width); 104 | [color set]; 105 | UIRectFill(rect); 106 | 107 | [image drawInRect:(CGRect){{width + 1, width + 1}, [image size]}]; 108 | 109 | // Fetch the image 110 | UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext(); 111 | 112 | UIGraphicsEndImageContext(); 113 | 114 | return renderedImage; 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /Container/MPFlipEnumerations.h: -------------------------------------------------------------------------------- 1 | // 2 | // MPFlipEnumerations.h 3 | // MPTransition (v1.1.0) 4 | // 5 | // Created by Mark Pospesel on 5/15/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #ifndef MPFoldTransition_MPFlipEnumerations_h 10 | #define MPFoldTransition_MPFlipEnumerations_h 11 | 12 | // Bit 0: Direction - Forward (unset) vs.Backward (set) 13 | // Forward = page flip from right to left (horizontal) or bottom to top (vertical) 14 | // Backward = page flip from left to right (horizontal) or top to bottom (vertical) 15 | 16 | // Bit 1: Orientation - Horizontal (unset) vs. Vertical (set) 17 | // Horizontal = page flips right to left about a vertical spine 18 | // Vertical = page flips bottom to top about a horizontal spine 19 | 20 | // Bit 2: Perspective - Normal (unset) vs. Reverse (set) 21 | // Normal = page flips towards viewer 22 | // Reverse = page flips away from viewer 23 | 24 | // TODO: spine position (left, mid, right // top, mid, bottom) 25 | 26 | enum { 27 | // current view folds away into center, next view slides in flat from top & bottom 28 | MPFlipStyleDefault = 0, 29 | MPFlipStyleDirectionBackward = 1 << 0, 30 | MPFlipStyleOrientationVertical = 1 << 1, 31 | MPFlipStylePerspectiveReverse = 1 << 2 32 | }; 33 | typedef NSUInteger MPFlipStyle; 34 | 35 | enum { 36 | MPFlipAnimationStage1 = 0, 37 | MPFlipAnimationStage2 = 1 38 | } typedef MPFlipAnimationStage; 39 | 40 | #define MPFlipStyleDirectionMask MPFlipStyleDirectionBackward 41 | #define MPFlipStyleOrientationMask MPFlipStyleOrientationVertical 42 | #define MPFlipStylePerspectiveMask MPFlipStylePerspectiveReverse 43 | 44 | static inline MPFlipStyle MPFlipStyleFlipDirectionBit(MPFlipStyle style) { return (style & ~MPFlipStyleDirectionMask) | ((style & MPFlipStyleDirectionMask) == MPFlipStyleDirectionBackward? 0 : MPFlipStyleDirectionBackward); } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Container/MPFlipTransition.h: -------------------------------------------------------------------------------- 1 | // 2 | // MPFlipTransition.h 3 | // MPTransition (v1.1.0) 4 | // 5 | // Created by Mark Pospesel on 5/15/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import "MPFlipEnumerations.h" 10 | #import "MPTransition.h" 11 | 12 | @interface MPFlipTransition : MPTransition 13 | 14 | #pragma mark - Properties 15 | 16 | @property (assign, nonatomic) MPFlipStyle style; 17 | @property (assign, nonatomic) CGFloat coveredPageShadowOpacity; 18 | @property (assign, nonatomic) CGFloat flippingPageShadowOpacity; 19 | @property (strong, nonatomic) UIColor *flipShadowColor; 20 | @property (readonly, nonatomic) MPFlipAnimationStage stage; 21 | @property (assign, nonatomic) CGFloat rubberbandMaximumProgress; // how far up rubberband animation should pull 22 | 23 | // Whether all 4 halves of to and from views should be rendered as bitmap contexts, or 24 | // if it should attempt to use the views themselves (with masks) for the facing and reveal pages 25 | // If YES, will always render all 4 page halves. 26 | // If NO, will attempt to only render the front and back page halves, and use the actual views to display the static page halves (facing and reveal). 27 | // In order for the destination view to be used for the reveal page, it must already be in the same view hierarchy as the source view or else completionAction must not be MPTransitionActionNone 28 | @property (assign, nonatomic) BOOL shouldRenderAllViews; 29 | 30 | #pragma mark - init 31 | 32 | - (id)initWithSourceView:(UIView *)sourceView destinationView:(UIView *)destinationView duration:(NSTimeInterval)duration style:(MPFlipStyle)style completionAction:(MPTransitionAction)action; 33 | 34 | #pragma mark - Instance methods 35 | 36 | // builds the layers for the flip animation 37 | - (void)buildLayers; 38 | 39 | // performs the flip animation 40 | - (void)perform:(void (^)(BOOL finished))completion; 41 | 42 | - (void)performRubberband:(void (^)(BOOL finished))completion; 43 | 44 | // set view to any position within either half of the animation 45 | // progress ranges from 0 (start) to 1 (complete) within each of 2 animation stages 46 | - (void)setStage:(MPFlipAnimationStage)stage progress:(CGFloat)progress; 47 | 48 | // moves layers into position for beginning of stage 2 (flip back page to vertical) 49 | - (void)prepareForStage2; 50 | 51 | - (void)animateFromProgress:(CGFloat)fromProgress shouldFallBack:(BOOL)shouldFallBack completion:(void (^)(BOOL finished))completion; 52 | 53 | #pragma mark - Class methods 54 | 55 | // For generic UIViewController transitions 56 | + (void)transitionFromViewController:(UIViewController *)fromController 57 | toViewController:(UIViewController *)toController 58 | duration:(NSTimeInterval)duration 59 | style:(MPFlipStyle)style 60 | completion:(void (^)(BOOL finished))completion; 61 | 62 | // For generic UIView transitions 63 | + (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration style:(MPFlipStyle)style transitionAction:(MPTransitionAction)action completion:(void (^)(BOOL finished))completion; 64 | 65 | // To present a view controller modally 66 | + (void)presentViewController:(UIViewController *)viewControllerToPresent from:(UIViewController *)presentingController duration:(NSTimeInterval)duration style:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion; 67 | 68 | // To dismiss a modal view controller 69 | + (void)dismissViewControllerFromPresentingController:(UIViewController *)presentingController duration:(NSTimeInterval)duration style:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion; 70 | 71 | @end 72 | 73 | #pragma mark - UIViewController extensions 74 | 75 | // Convenience method extensions for UIViewController 76 | @interface UIViewController(MPFlipTransition) 77 | 78 | // present view controller modally with fold transition 79 | // use like presentViewController:animated:completion: 80 | - (void)presentViewController:(UIViewController *)viewControllerToPresent flipStyle:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion; 81 | 82 | // dismiss presented controller with fold transition 83 | // use like dismissViewControllerAnimated:completion: 84 | - (void)dismissViewControllerWithFlipStyle:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion; 85 | 86 | @end 87 | 88 | #pragma mark - UINavigationController extensions 89 | 90 | // Convenience method extensions for UINavigationController 91 | @interface UINavigationController(MPFlipTransition) 92 | 93 | //- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated 94 | - (void)pushViewController:(UIViewController *)viewController flipStyle:(MPFlipStyle)style; 95 | 96 | //- (UIViewController *)popViewControllerAnimated:(BOOL)animated; 97 | - (UIViewController *)popViewControllerWithFlipStyle:(MPFlipStyle)style; 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /Container/MPFlipTransition.m: -------------------------------------------------------------------------------- 1 | // 2 | // MPFlipTransition.m 3 | // MPTransition (v1.1.4) 4 | // 5 | // Created by Mark Pospesel on 5/15/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #define DEFAULT_COVERED_PAGE_SHADOW_OPACITY (1./3) 10 | #define DEFAULT_FLIPPING_PAGE_SHADOW_OPACITY 0.1 11 | #define DEFAULT_RUBBERBAND_MAX_PROGRESS (1./3) 12 | 13 | #import "MPFlipTransition.h" 14 | #import "MPAnimation.h" 15 | #import 16 | #include 17 | 18 | static inline double mp_radians (double degrees) {return degrees * M_PI/180;} 19 | 20 | @interface MPFlipTransition() 21 | 22 | @property (assign, nonatomic, getter = wasDestinationViewShown) BOOL destinationViewShown; 23 | @property (assign, nonatomic, getter = wereLayersBuilt) BOOL layersBuilt; 24 | @property (strong, nonatomic) UIView *animationView; 25 | @property (strong, nonatomic) CALayer *layerFront; 26 | @property (strong, nonatomic) CALayer *layerFacing; 27 | @property (strong, nonatomic) CALayer *layerBack; 28 | @property (strong, nonatomic) CALayer *layerReveal; 29 | @property (strong, nonatomic) CAShapeLayer *revealLayerMask; 30 | @property (strong, nonatomic) CAGradientLayer *layerFrontShadow; 31 | @property (strong, nonatomic) CAGradientLayer *layerBackShadow; 32 | @property (strong, nonatomic) CALayer *layerFacingShadow; 33 | @property (strong, nonatomic) CALayer *layerRevealShadow; 34 | @property (assign, nonatomic) MPFlipAnimationStage stage; 35 | @property (weak, nonatomic) UIView *actingSource; 36 | @property (assign, nonatomic) CGRect sourceFrame; 37 | 38 | @end 39 | 40 | @implementation MPFlipTransition 41 | 42 | #pragma mark - Properties 43 | 44 | @synthesize destinationViewShown = _destinationViewShown; 45 | @synthesize layersBuilt = _layersBuilt; 46 | @synthesize animationView = _animationView; 47 | @synthesize layerFront = _layerFront; 48 | @synthesize layerFacing = _layerFacing; 49 | @synthesize layerBack = _layerBack; 50 | @synthesize layerReveal = _layerReveal; 51 | @synthesize revealLayerMask = _revealLayerMask; 52 | @synthesize layerFrontShadow = _layerFrontShadow; 53 | @synthesize layerBackShadow = _layerBackShadow; 54 | @synthesize layerFacingShadow = _layerFacingShadow; 55 | @synthesize layerRevealShadow = _layerRevealShadow; 56 | @synthesize stage = _stage; 57 | @synthesize actingSource = _actingSource; 58 | @synthesize sourceFrame = _sourceFrame; 59 | 60 | @synthesize style = _style; 61 | @synthesize coveredPageShadowOpacity = _coveredPageShadowOpacity; 62 | @synthesize flippingPageShadowOpacity = _flippingPageShadowOpacity; 63 | @synthesize flipShadowColor = _flipShadowColor; 64 | @synthesize rubberbandMaximumProgress = _rubberbandMaximumProgress; 65 | @synthesize shouldRenderAllViews = _shouldRenderAllViews; 66 | 67 | #pragma mark - init 68 | 69 | - (id)initWithSourceView:(UIView *)sourceView destinationView:(UIView *)destinationView duration:(NSTimeInterval)duration style:(MPFlipStyle)style completionAction:(MPTransitionAction)action { 70 | self = [super initWithSourceView:sourceView destinationView:destinationView duration:duration timingCurve:UIViewAnimationCurveEaseInOut completionAction:action]; 71 | if (self) 72 | { 73 | _style = style; 74 | _coveredPageShadowOpacity = DEFAULT_COVERED_PAGE_SHADOW_OPACITY; 75 | _flippingPageShadowOpacity = DEFAULT_FLIPPING_PAGE_SHADOW_OPACITY; 76 | _flipShadowColor = [UIColor blackColor]; 77 | _layersBuilt = NO; 78 | _stage = MPFlipAnimationStage1; 79 | _rubberbandMaximumProgress = DEFAULT_RUBBERBAND_MAX_PROGRESS; 80 | _shouldRenderAllViews = YES; 81 | } 82 | 83 | return self; 84 | } 85 | 86 | #pragma mark - Instance methods 87 | 88 | // We split the animation into 2 parts, so don't ease out on the 1st half (we'll do that in 2nd half) 89 | - (NSString *)timingCurveFunctionNameFirstHalf 90 | { 91 | switch ([self timingCurve]) { 92 | case UIViewAnimationCurveEaseIn: 93 | case UIViewAnimationCurveEaseInOut: 94 | return kCAMediaTimingFunctionEaseIn; 95 | 96 | case UIViewAnimationCurveEaseOut: 97 | case UIViewAnimationCurveLinear: 98 | return kCAMediaTimingFunctionLinear; 99 | } 100 | 101 | return kCAMediaTimingFunctionEaseIn; 102 | } 103 | 104 | // We split the animation into 2 parts, so don't ease in on the 2nd half (we did that in the 1st half) 105 | - (NSString *)timingCurveFunctionNameSecondHalf 106 | { 107 | switch ([self timingCurve]) { 108 | case UIViewAnimationCurveEaseOut: 109 | case UIViewAnimationCurveEaseInOut: 110 | return kCAMediaTimingFunctionEaseOut; 111 | 112 | case UIViewAnimationCurveEaseIn: 113 | case UIViewAnimationCurveLinear: 114 | return kCAMediaTimingFunctionLinear; 115 | } 116 | 117 | return kCAMediaTimingFunctionEaseOut; 118 | } 119 | 120 | // switching between the 2 halves of the animation - between front and back sides of the page we're turning 121 | - (void)switchToStage:(MPFlipAnimationStage)flipStage 122 | { 123 | // 0 = stage 1, 1 = stage 2 124 | [CATransaction begin]; 125 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 126 | 127 | if (flipStage == MPFlipAnimationStage1) 128 | { 129 | [self prepareForStage2]; 130 | [self.animationView.layer insertSublayer:self.layerFacing above:self.layerReveal]; // re-order these 2 layers 131 | [self.animationView.layer insertSublayer:self.layerFront below:self.layerFacing]; 132 | [self.layerReveal addSublayer:self.layerRevealShadow]; 133 | 134 | [self.layerBack removeFromSuperlayer]; 135 | [self.layerFacingShadow removeFromSuperlayer]; 136 | } 137 | else 138 | { 139 | [self doFlip1:1]; 140 | [self.animationView.layer insertSublayer:self.layerReveal above:self.layerFacing]; // re-order these 2 layers 141 | [self.animationView.layer insertSublayer:self.layerBack below:self.layerReveal]; 142 | [self.layerFacing addSublayer:self.layerFacingShadow]; 143 | 144 | [self.layerFront removeFromSuperlayer]; 145 | [self.layerRevealShadow removeFromSuperlayer]; 146 | } 147 | 148 | [CATransaction commit]; 149 | } 150 | 151 | - (void)buildLayers 152 | { 153 | [self buildLayers:NO]; 154 | } 155 | 156 | - (void)buildLayers:(BOOL)isResizing 157 | { 158 | if (!isResizing && [self wereLayersBuilt]) 159 | return; 160 | 161 | BOOL forwards = ([self style] & MPFlipStyleDirectionMask) != MPFlipStyleDirectionBackward; 162 | BOOL vertical = ([self style] & MPFlipStyleOrientationMask) == MPFlipStyleOrientationVertical; 163 | BOOL inward = ([self style] & MPFlipStylePerspectiveMask) == MPFlipStylePerspectiveReverse; 164 | BOOL isRubberbanding = !self.destinationView; 165 | 166 | CGRect bounds = self.rect; 167 | CGFloat scale = [[UIScreen mainScreen] scale]; 168 | 169 | // we inset the panels 1 point on each side with a transparent margin to antialiase the edges 170 | UIEdgeInsets insets = vertical? UIEdgeInsetsMake(0, 1, 0, 1) : UIEdgeInsetsMake(1, 0, 1, 0); 171 | 172 | CGRect upperRect = bounds; 173 | if (vertical) 174 | upperRect.size.height = bounds.size.height / 2; 175 | else 176 | upperRect.size.width = bounds.size.width / 2; 177 | CGRect lowerRect = upperRect; 178 | BOOL isOddSize = vertical? (upperRect.size.height != (roundf(upperRect.size.height * scale)/scale)) : (upperRect.size.width != (roundf(upperRect.size.width * scale) / scale)); 179 | if (isOddSize) 180 | { 181 | // If view has an odd height, make the 2 panels of integer height with top panel 1 pixel taller (see below) 182 | if (vertical) 183 | { 184 | upperRect.size.height = (roundf(upperRect.size.height * scale)/scale); 185 | lowerRect.size.height = bounds.size.height - upperRect.size.height; 186 | } 187 | else 188 | { 189 | upperRect.size.width = (roundf(upperRect.size.width * scale)/scale); 190 | lowerRect.size.width = bounds.size.width - upperRect.size.width; 191 | } 192 | } 193 | if (vertical) 194 | lowerRect.origin.y += upperRect.size.height; 195 | else 196 | lowerRect.origin.x += upperRect.size.width; 197 | 198 | if (![self isDimissing] && !isRubberbanding) 199 | self.destinationView.bounds = (CGRect){CGPointZero, bounds.size}; 200 | 201 | CGRect destUpperRect = CGRectOffset(upperRect, -upperRect.origin.x, -upperRect.origin.y); 202 | CGRect destLowerRect = CGRectOffset(lowerRect, -upperRect.origin.x, -upperRect.origin.y); 203 | 204 | if ([self isDimissing] && !isRubberbanding) 205 | { 206 | CGFloat x = self.destinationView.bounds.size.width - bounds.size.width; 207 | CGFloat y = self.destinationView.bounds.size.height - bounds.size.height; 208 | destUpperRect.origin.x += x; 209 | destLowerRect.origin.x += x; 210 | destUpperRect.origin.y += y; 211 | destLowerRect.origin.y += y; 212 | [self setRect:CGRectOffset([self rect], x, y)]; 213 | } 214 | 215 | // Create 4 images to represent 2 halves of the 2 views 216 | 217 | // The page flip animation is broken into 2 halves 218 | // 1. Flip old page up to vertical 219 | // 2. Flip new page from vertical down to flat 220 | // as we pass the halfway point of the animation, the "page" switches from old to new 221 | 222 | // front Page = the half of current view we are flipping during 1st half 223 | // facing Page = the other half of the current view (doesn't move, gets covered by back page during 2nd half) 224 | // back Page = the half of the next view that appears on the flipping page during 2nd half 225 | // reveal Page = the other half of the next view (doesn't move, gets revealed by front page during 1st half) 226 | UIImage *pageFrontImage = [MPAnimation renderImageFromView:self.sourceView withRect:forwards? lowerRect : upperRect transparentInsets:insets]; 227 | 228 | self.actingSource = [self sourceView]; // the view that is already part of the view hierarchy 229 | UIView *containerView = [self.actingSource superview]; 230 | if (!containerView && !isRubberbanding) 231 | { 232 | // in case of dismissal, it is actually the destination view since we had to add it 233 | // in order to get it to render correctly 234 | self.actingSource = [self destinationView]; 235 | containerView = [self.actingSource superview]; 236 | } 237 | 238 | if (!isResizing) 239 | { 240 | [self.actingSource addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil]; 241 | self.sourceFrame = [self.actingSource frame]; 242 | } 243 | 244 | BOOL isDestinationViewAbove = !isRubberbanding; 245 | BOOL isModal = [containerView isKindOfClass:[UIWindow class]]; 246 | BOOL drawFacing = [self shouldRenderAllViews], drawReveal = [self shouldRenderAllViews]; 247 | 248 | switch (self.completionAction) 249 | { 250 | case MPTransitionActionAddRemove: 251 | if (!isModal && !isRubberbanding) 252 | [self.destinationView setFrame:[self.sourceView frame]]; 253 | if (!isRubberbanding && !isResizing) 254 | [containerView addSubview:self.destinationView]; 255 | break; 256 | 257 | case MPTransitionActionShowHide: 258 | [self.destinationView setHidden:NO]; 259 | isDestinationViewAbove = isRubberbanding? NO : [self.destinationView isAboveSiblingView:self.sourceView]; 260 | break; 261 | 262 | case MPTransitionActionNone: 263 | if ([self.destinationView superview] == [self.sourceView superview]) 264 | { 265 | isDestinationViewAbove = [self.destinationView isAboveSiblingView:self.sourceView]; 266 | if ([self.destinationView isHidden]) 267 | { 268 | [self.destinationView setHidden:NO]; 269 | [self setDestinationViewShown:YES]; 270 | } 271 | } 272 | else if (![self.sourceView superview]) 273 | { 274 | drawFacing = YES; 275 | } 276 | else 277 | { 278 | drawReveal = !isRubberbanding; 279 | if ([self.destinationView isHidden]) 280 | { 281 | [self.destinationView setHidden:NO]; 282 | [self setDestinationViewShown:YES]; 283 | } 284 | } 285 | break; 286 | } 287 | 288 | UIImage *pageFacingImage = drawFacing? [MPAnimation renderImageFromView:self.sourceView withRect:forwards? upperRect : lowerRect] : nil; 289 | 290 | UIImage *pageBackImage = isRubberbanding? nil : [MPAnimation renderImageFromView:self.destinationView withRect:forwards? destUpperRect : destLowerRect transparentInsets:insets]; 291 | UIImage *pageRevealImage = drawReveal? [MPAnimation renderImageFromView:self.destinationView withRect:forwards? destLowerRect : destUpperRect] : nil; 292 | 293 | CATransform3D transform = CATransform3DIdentity; 294 | 295 | CGFloat width = vertical? bounds.size.width : bounds.size.height; 296 | CGFloat height = vertical? bounds.size.height/2 : bounds.size.width/2; 297 | CGFloat upperHeight = roundf(height * scale) / scale; // round heights to integer for odd height 298 | 299 | // view to hold all our sublayers 300 | CGRect mainRect = [containerView convertRect:self.rect fromView:self.actingSource]; 301 | CGPoint center = (CGPoint){CGRectGetMidX(mainRect), CGRectGetMidY(mainRect)}; 302 | if (isModal) 303 | mainRect = [self.actingSource convertRect:mainRect fromView:nil]; 304 | if (!isResizing) 305 | self.animationView = [[UIView alloc] initWithFrame:mainRect]; 306 | else 307 | self.animationView.frame = mainRect; 308 | self.animationView.backgroundColor = [UIColor clearColor]; 309 | self.animationView.transform = self.actingSource.transform; 310 | self.animationView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 311 | if (!isResizing) 312 | [containerView addSubview:self.animationView]; 313 | if (isModal) 314 | { 315 | [self.animationView.layer setPosition:center]; 316 | } 317 | 318 | if (!isRubberbanding) 319 | { 320 | if (!isResizing) 321 | self.layerReveal = [CALayer layer]; 322 | self.layerReveal.bounds = (CGRect){CGPointZero, drawReveal? pageRevealImage.size : forwards? destLowerRect.size : destUpperRect.size}; 323 | self.layerReveal.anchorPoint = CGPointMake(vertical? 0.5 : forwards? 0 : 1, vertical? forwards? 0 : 1 : 0.5); 324 | self.layerReveal.position = CGPointMake(vertical? width/2 : upperHeight, vertical? upperHeight : width/2); 325 | if (drawReveal) 326 | [self.layerReveal setContents:(id)[pageRevealImage CGImage]]; 327 | if (!isResizing) 328 | [self.animationView.layer addSublayer:self.layerReveal]; 329 | } 330 | 331 | if (!isResizing) 332 | self.layerFacing = [CALayer layer]; 333 | self.layerFacing.bounds = (CGRect){CGPointZero, drawFacing? pageFacingImage.size : forwards? upperRect.size : lowerRect.size}; 334 | self.layerFacing.anchorPoint = CGPointMake(vertical? 0.5 : forwards? 1 : 0, vertical? forwards? 1 : 0 : 0.5); 335 | self.layerFacing.position = CGPointMake(vertical? width/2 : upperHeight, vertical? upperHeight : width/2); 336 | if (drawFacing) 337 | [self.layerFacing setContents:(id)[pageFacingImage CGImage]]; 338 | if (!isResizing) 339 | [self.animationView.layer addSublayer:self.layerFacing]; 340 | 341 | if (![self shouldRenderAllViews] || isRubberbanding) 342 | { 343 | if (!isResizing) 344 | self.revealLayerMask = [CAShapeLayer layer]; 345 | CGRect maskRect = (forwards == isDestinationViewAbove)? destLowerRect : destUpperRect; 346 | self.revealLayerMask.path = [[UIBezierPath bezierPathWithRect:maskRect] CGPath]; 347 | UIView *viewToMask = isDestinationViewAbove? self.destinationView : self.sourceView; 348 | [viewToMask.layer setMask:self.revealLayerMask]; 349 | } 350 | 351 | if (!isResizing) 352 | self.layerFront = [CALayer layer]; 353 | self.layerFront.bounds = (CGRect){CGPointZero, pageFrontImage.size}; 354 | self.layerFront.anchorPoint = CGPointMake(vertical? 0.5 : forwards? 0 : 1, vertical? forwards? 0 : 1 : 0.5); 355 | self.layerFront.position = CGPointMake(vertical? width/2 : upperHeight, vertical? upperHeight : width/2); 356 | [self.layerFront setContents:(id)[pageFrontImage CGImage]]; 357 | if (!isResizing) 358 | [self.animationView.layer addSublayer:self.layerFront]; 359 | 360 | if (!isRubberbanding) 361 | { 362 | if (!isResizing) 363 | self.layerBack = [CALayer layer]; 364 | self.layerBack.bounds = (CGRect){CGPointZero, pageBackImage.size}; 365 | self.layerBack.anchorPoint = CGPointMake(vertical? 0.5 : forwards? 1 : 0, vertical? forwards? 1 : 0 : 0.5); 366 | self.layerBack.position = CGPointMake(vertical? width/2 : upperHeight, vertical? upperHeight : width/2); 367 | [self.layerBack setContents:(id)[pageBackImage CGImage]]; 368 | } 369 | 370 | // Create shadow layers 371 | if (!isResizing) 372 | { 373 | self.layerFrontShadow = [CAGradientLayer layer]; 374 | [self.layerFront addSublayer:self.layerFrontShadow]; 375 | self.layerFrontShadow.opacity = 0.0; 376 | if (forwards) 377 | self.layerFrontShadow.colors = [NSArray arrayWithObjects:(id)[[[self flipShadowColor] colorWithAlphaComponent:0.5] CGColor], (id)[self flipShadowColor].CGColor, (id)[[UIColor clearColor] CGColor], nil]; 378 | else 379 | self.layerFrontShadow.colors = [NSArray arrayWithObjects:(id)[[UIColor clearColor] CGColor], (id)[self flipShadowColor].CGColor, (id)[[[self flipShadowColor] colorWithAlphaComponent:0.5] CGColor], nil]; 380 | self.layerFrontShadow.startPoint = CGPointMake(vertical? 0.5 : forwards? 0 : 0.5, vertical? forwards? 0 : 0.5 : 0.5); 381 | self.layerFrontShadow.endPoint = CGPointMake(vertical? 0.5 : forwards? 0.5 : 1, vertical? forwards? 0.5 : 1 : 0.5); 382 | self.layerFrontShadow.locations = [NSArray arrayWithObjects:[NSNumber numberWithDouble:0], [NSNumber numberWithDouble:forwards? 0.1 : 0.9], [NSNumber numberWithDouble:1], nil]; 383 | } 384 | self.layerFrontShadow.frame = CGRectInset(self.layerFront.bounds, insets.left, insets.top); 385 | 386 | if (!isRubberbanding) 387 | { 388 | if (!isResizing) 389 | { 390 | self.layerBackShadow = [CAGradientLayer layer]; 391 | [self.layerBack addSublayer:self.layerBackShadow]; 392 | self.layerBackShadow.opacity = [self flippingPageShadowOpacity]; 393 | if (forwards) 394 | self.layerBackShadow.colors = [NSArray arrayWithObjects:(id)[[UIColor clearColor] CGColor], (id)[self flipShadowColor].CGColor, (id)[[[self flipShadowColor] colorWithAlphaComponent:0.5] CGColor], nil]; 395 | else 396 | self.layerBackShadow.colors = [NSArray arrayWithObjects:(id)[[[self flipShadowColor] colorWithAlphaComponent:0.5] CGColor], (id)[self flipShadowColor].CGColor, (id)[[UIColor clearColor] CGColor], nil]; 397 | self.layerBackShadow.startPoint = CGPointMake(vertical? 0.5 : forwards? 0.5 : 0, vertical? forwards? 0.5 : 0 : 0.5); 398 | self.layerBackShadow.endPoint = CGPointMake(vertical? 0.5 : forwards? 1 : 0.5, vertical? forwards? 1 : 0.5 : 0.5); 399 | self.layerBackShadow.locations = [NSArray arrayWithObjects:[NSNumber numberWithDouble:0], [NSNumber numberWithDouble:forwards? 0.9 : 0.1], [NSNumber numberWithDouble:1], nil]; 400 | } 401 | self.layerBackShadow.frame = CGRectInset(self.layerBack.bounds, insets.left, insets.top); 402 | } 403 | 404 | if (!inward) 405 | { 406 | if (!isRubberbanding) 407 | { 408 | if (!isResizing) 409 | { 410 | self.layerRevealShadow = [CALayer layer]; 411 | [self.layerReveal addSublayer:self.layerRevealShadow]; 412 | self.layerRevealShadow.backgroundColor = [self flipShadowColor].CGColor; 413 | self.layerRevealShadow.opacity = [self coveredPageShadowOpacity]; 414 | } 415 | self.layerRevealShadow.frame = self.layerReveal.bounds; 416 | } 417 | 418 | if (!isResizing) 419 | { 420 | self.layerFacingShadow = [CALayer layer]; 421 | //[self.layerFacing addSublayer:self.layerFacingShadow]; // add later 422 | self.layerFacingShadow.backgroundColor = [self flipShadowColor].CGColor; 423 | self.layerFacingShadow.opacity = 0.0; 424 | } 425 | self.layerFacingShadow.frame = self.layerFacing.bounds; 426 | } 427 | 428 | // Perspective is best proportional to the height of the pieces being folded away, rather than a fixed value 429 | // the larger the piece being folded, the more perspective distance (zDistance) is needed. 430 | // m34 = -1/zDistance 431 | if ([self m34] == INFINITY) 432 | transform.m34 = -1.0/(height * 4.6666667); 433 | else 434 | transform.m34 = [self m34]; 435 | if (inward) 436 | transform.m34 = -transform.m34; // flip perspective around 437 | self.animationView.layer.sublayerTransform = transform; 438 | 439 | [self setLayersBuilt:YES]; 440 | } 441 | 442 | - (void)cleanupLayers 443 | { 444 | // cleanup 445 | if (![self wereLayersBuilt]) 446 | return; 447 | 448 | [self.animationView removeFromSuperview]; 449 | 450 | [CATransaction begin]; 451 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 452 | [self.revealLayerMask removeFromSuperlayer]; // don't animate 453 | [CATransaction commit]; 454 | 455 | self.animationView = nil; 456 | self.layerFront = nil; 457 | self.layerBack = nil; 458 | self.layerFacing = nil; 459 | self.layerReveal = nil; 460 | self.layerFrontShadow = nil; 461 | self.layerBackShadow = nil; 462 | self.layerFacingShadow = nil; 463 | self.layerRevealShadow = nil; 464 | self.revealLayerMask = nil; 465 | 466 | [self.actingSource removeObserver:self forKeyPath:@"frame"]; 467 | self.actingSource = nil; 468 | 469 | [self setLayersBuilt:NO]; 470 | } 471 | 472 | - (void)perform:(void (^)(BOOL finished))completion 473 | { 474 | [self buildLayers]; 475 | [self prepareForStage2]; // set back page to vertical 476 | [self animateFlip1:NO fromProgress:0 withCompletion:completion]; 477 | } 478 | 479 | - (void)performRubberband:(void (^)(BOOL finished))completion 480 | { 481 | [self buildLayers]; 482 | CGFloat maxProgress = [self rubberbandMaximumProgress]; 483 | [self setDuration:[self duration] * 1 / maxProgress]; // necessary to arrive at the dersired total duration 484 | [self animateFlip1:NO fromProgress:0 toProgress:maxProgress withCompletion:^(BOOL finished) { 485 | [self animateFlip1:YES fromProgress:maxProgress toProgress:0 withCompletion:^(BOOL finished) { 486 | [self cleanupLayers]; 487 | [self transitionDidComplete:NO]; 488 | 489 | if (completion) 490 | completion(finished); // execute the completion block that was passed in 491 | }]; 492 | }]; 493 | } 494 | 495 | - (void)animateFlip1:(BOOL)isFallingBack fromProgress:(CGFloat)fromProgress withCompletion:(void (^)(BOOL finished))completion 496 | { 497 | [self animateFlip1:isFallingBack fromProgress:fromProgress toProgress:1.0 withCompletion:completion]; 498 | } 499 | 500 | - (void)animateFlip1:(BOOL)isFallingBack fromProgress:(CGFloat)fromProgress toProgress:(CGFloat)toProgress withCompletion:(void (^)(BOOL finished))completion 501 | { 502 | BOOL forwards = ([self style] & MPFlipStyleDirectionMask) != MPFlipStyleDirectionBackward; 503 | BOOL vertical = ([self style] & MPFlipStyleOrientationMask) == MPFlipStyleOrientationVertical; 504 | BOOL inward = ([self style] & MPFlipStylePerspectiveMask) == MPFlipStylePerspectiveReverse; 505 | BOOL isRubberbanding = !self.destinationView; 506 | 507 | // 2-stage animation 508 | CALayer *layer = isFallingBack && !isRubberbanding? self.layerBack : self.layerFront; 509 | CALayer *flippingShadow = isFallingBack && !isRubberbanding? self.layerBackShadow : self.layerFrontShadow; 510 | CALayer *coveredShadow = isFallingBack && !isRubberbanding? self.layerFacingShadow : self.layerRevealShadow; 511 | 512 | if (isFallingBack && !isRubberbanding) 513 | fromProgress = 1 - fromProgress; 514 | 515 | // Figure out how many frames we want 516 | CGFloat duration = (self.duration / 2) * fabsf(toProgress - fromProgress); 517 | NSUInteger frameCount = ceilf(duration * 60); // Let's shoot for 60 FPS to ensure proper sine curve approximation 518 | 519 | NSString *rotationKey = vertical? @"transform.rotation.x" : @"transform.rotation.y"; 520 | double factor = (isFallingBack && !isRubberbanding? -1 : 1) * (forwards? -1 : 1) * (vertical? -1 : 1) * M_PI / 180; 521 | CGFloat coveredPageShadowOpacity = [self coveredPageShadowOpacity]; 522 | 523 | // Create a transaction 524 | [CATransaction begin]; 525 | [CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration]; 526 | [CATransaction setValue:[CAMediaTimingFunction functionWithName:isRubberbanding? kCAMediaTimingFunctionEaseInEaseOut : fromProgress > 0? kCAMediaTimingFunctionLinear : [self timingCurveFunctionNameFirstHalf]] forKey:kCATransactionAnimationTimingFunction]; 527 | [CATransaction setCompletionBlock:^{ 528 | // 2nd half of animation, once 1st half completes 529 | if (toProgress == 1) 530 | { 531 | [self setStage:isFallingBack? MPFlipAnimationStage1 : MPFlipAnimationStage2]; 532 | [self switchToStage:isFallingBack? MPFlipAnimationStage1 : MPFlipAnimationStage2]; 533 | 534 | [self animateFlip2:isFallingBack fromProgress:isFallingBack? 1 : 0 withCompletion:completion]; 535 | } 536 | else 537 | { 538 | if (completion) 539 | completion(YES); 540 | } 541 | }]; 542 | 543 | // First Half of Animation 544 | 545 | // Flip front page from flat up to vertical 546 | CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:rotationKey]; 547 | [animation setFromValue:[NSNumber numberWithDouble:90 * factor * fromProgress]]; 548 | [animation setToValue:[NSNumber numberWithDouble:90 * factor * toProgress]]; 549 | [animation setFillMode:kCAFillModeForwards]; 550 | [animation setRemovedOnCompletion:NO]; 551 | [layer addAnimation:animation forKey:nil]; 552 | [layer setTransform:CATransform3DMakeRotation(90*factor*toProgress, vertical? 1 : 0, vertical? 0 : 1, 0)]; 553 | 554 | // Shadows 555 | 556 | // darken front page just slightly as we flip (just to give it a crease where it touches facing page) 557 | animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 558 | [animation setFromValue:[NSNumber numberWithDouble:[self flippingPageShadowOpacity] * fromProgress]]; 559 | [animation setToValue:[NSNumber numberWithDouble:[self flippingPageShadowOpacity] * toProgress]]; 560 | [animation setFillMode:kCAFillModeForwards]; 561 | [animation setRemovedOnCompletion:NO]; 562 | [flippingShadow addAnimation:animation forKey:nil]; 563 | [flippingShadow setOpacity:[self flippingPageShadowOpacity] * toProgress]; 564 | 565 | if (!inward && !isRubberbanding) 566 | { 567 | // lighten the page that is revealed by front page flipping up (along a cosine curve) 568 | // TODO: consider FROM value 569 | NSMutableArray* arrayOpacity = [NSMutableArray arrayWithCapacity:frameCount + 1]; 570 | CGFloat progress; 571 | CGFloat cosOpacity; 572 | for (int frame = 0; frame <= frameCount; frame++) 573 | { 574 | progress = fromProgress + (toProgress - fromProgress) * ((float)frame) / frameCount; 575 | cosOpacity = cos(mp_radians(90 * progress)) * coveredPageShadowOpacity; 576 | if (frame == frameCount) 577 | cosOpacity = 0; 578 | [arrayOpacity addObject:[NSNumber numberWithFloat:cosOpacity]]; 579 | } 580 | 581 | CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; 582 | [keyAnimation setValues:[NSArray arrayWithArray:arrayOpacity]]; 583 | [keyAnimation setFillMode:kCAFillModeForwards]; 584 | [keyAnimation setRemovedOnCompletion:NO]; 585 | [coveredShadow addAnimation:keyAnimation forKey:nil]; 586 | [coveredShadow setOpacity:[[arrayOpacity lastObject] floatValue]]; 587 | } 588 | 589 | // Commit the transaction for 1st half 590 | [CATransaction commit]; 591 | } 592 | 593 | - (void)animateFlip2:(BOOL)isFallingBack fromProgress:(CGFloat)fromProgress withCompletion:(void (^)(BOOL finished))completion 594 | { 595 | // Second half of animation 596 | BOOL forwards = ([self style] & MPFlipStyleDirectionMask) != MPFlipStyleDirectionBackward; 597 | BOOL vertical = ([self style] & MPFlipStyleOrientationMask) == MPFlipStyleOrientationVertical; 598 | BOOL inward = ([self style] & MPFlipStylePerspectiveMask) == MPFlipStylePerspectiveReverse; 599 | 600 | // 1-stage animation 601 | CALayer *layer = isFallingBack? self.layerFront : self.layerBack; 602 | CALayer *flippingShadow = isFallingBack? self.layerFrontShadow : self.layerBackShadow; 603 | CALayer *coveredShadow = isFallingBack? self.layerRevealShadow : self.layerFacingShadow; 604 | 605 | NSUInteger frameCount = ceilf((self.duration / 2) * 60); // Let's shoot for 60 FPS to ensure proper sine curve approximation 606 | 607 | NSString *rotationKey = vertical? @"transform.rotation.x" : @"transform.rotation.y"; 608 | double factor = (isFallingBack? -1 : 1) * (forwards? -1 : 1) * (vertical? -1 : 1) * M_PI / 180; 609 | CGFloat coveredPageShadowOpacity = [self coveredPageShadowOpacity]; 610 | 611 | if (isFallingBack) 612 | fromProgress = 1 - fromProgress; 613 | CGFloat toProgress = 1; 614 | 615 | // Create a transaction 616 | [CATransaction begin]; 617 | [CATransaction setValue:[NSNumber numberWithFloat:self.duration/2] forKey:kCATransactionAnimationDuration]; 618 | [CATransaction setValue:[CAMediaTimingFunction functionWithName:[self timingCurveFunctionNameSecondHalf]] forKey:kCATransactionAnimationTimingFunction]; 619 | [CATransaction setCompletionBlock:^{ 620 | // This is the final completion block, when 2nd half of animation finishes 621 | [self cleanupLayers]; 622 | [self transitionDidComplete:!isFallingBack]; 623 | 624 | if (completion) 625 | completion(YES); // execute the completion block that was passed in 626 | }]; 627 | 628 | // Flip back page from vertical down to flat 629 | CABasicAnimation* animation2 = [CABasicAnimation animationWithKeyPath:rotationKey]; 630 | [animation2 setFromValue:[NSNumber numberWithDouble:-90*factor*(1-fromProgress)]]; 631 | [animation2 setToValue:[NSNumber numberWithDouble:0]]; 632 | [animation2 setFillMode:kCAFillModeForwards]; 633 | [animation2 setRemovedOnCompletion:NO]; 634 | [layer addAnimation:animation2 forKey:nil]; 635 | [layer setTransform:CATransform3DIdentity]; 636 | 637 | // Shadows 638 | 639 | // Lighten back page just slightly as we flip (just to give it a crease where it touches reveal page) 640 | animation2 = [CABasicAnimation animationWithKeyPath:@"opacity"]; 641 | [animation2 setFromValue:[NSNumber numberWithDouble:[self flippingPageShadowOpacity] * (1-fromProgress)]]; 642 | [animation2 setToValue:[NSNumber numberWithDouble:0]]; 643 | [animation2 setFillMode:kCAFillModeForwards]; 644 | [animation2 setRemovedOnCompletion:NO]; 645 | [flippingShadow addAnimation:animation2 forKey:nil]; 646 | [flippingShadow setOpacity:0]; 647 | 648 | if (!inward) 649 | { 650 | // Darken facing page as it gets covered by back page flipping down (along a sine curve) 651 | NSMutableArray* arrayOpacity = [NSMutableArray arrayWithCapacity:frameCount + 1]; 652 | CGFloat progress; 653 | CGFloat sinOpacity; 654 | for (int frame = 0; frame <= frameCount; frame++) 655 | { 656 | progress = fromProgress + (toProgress - fromProgress) * ((float)frame) / frameCount; 657 | sinOpacity = (sin(mp_radians(90 * progress))* coveredPageShadowOpacity); 658 | if (frame == 0) 659 | sinOpacity = 0; 660 | [arrayOpacity addObject:[NSNumber numberWithFloat:sinOpacity]]; 661 | } 662 | 663 | CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; 664 | [keyAnimation setValues:[NSArray arrayWithArray:arrayOpacity]]; 665 | [keyAnimation setFillMode:kCAFillModeForwards]; 666 | [keyAnimation setRemovedOnCompletion:NO]; 667 | [coveredShadow addAnimation:keyAnimation forKey:nil]; 668 | [coveredShadow setOpacity:[[arrayOpacity lastObject] floatValue]]; 669 | } 670 | 671 | // Commit the transaction for 2nd half 672 | [CATransaction commit]; 673 | } 674 | 675 | // set view to any position within either half of the animation 676 | // progress ranges from 0 (start) to 1 (complete) within each of 2 animation stages 677 | - (void)setStage:(MPFlipAnimationStage)stage progress:(CGFloat)progress 678 | { 679 | if ([self stage] != stage) 680 | { 681 | [self setStage:stage]; 682 | [self switchToStage:stage]; 683 | } 684 | 685 | if (stage == MPFlipAnimationStage1) 686 | [self doFlip1:progress]; 687 | else 688 | [self doFlip2:progress]; 689 | } 690 | 691 | // moves layers into position for beginning of stage 2 (flip back page to vertical) 692 | - (void)prepareForStage2 693 | { 694 | [self doFlip2:0]; 695 | } 696 | 697 | // set view to any position within the 1st half of the animation 698 | // progress ranges from 0 (start) to 1 (complete) 699 | - (void)doFlip1:(CGFloat)progress 700 | { 701 | [CATransaction begin]; 702 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 703 | 704 | if (progress < 0) 705 | progress = 0; 706 | else if (progress > 1) 707 | progress = 1; 708 | 709 | [self.layerFront setTransform:[self flipTransform1:progress]]; 710 | [self.layerFrontShadow setOpacity:[self flippingPageShadowOpacity] * progress]; 711 | CGFloat cosOpacity = cos(mp_radians(90 * progress)) * [self coveredPageShadowOpacity]; 712 | [self.layerRevealShadow setOpacity:cosOpacity]; 713 | 714 | [CATransaction commit]; 715 | } 716 | 717 | // set view to any position within the 2nd half of the animation 718 | // progress ranges from 0 (start) to 1 (complete) 719 | - (void)doFlip2:(CGFloat)progress 720 | { 721 | [CATransaction begin]; 722 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 723 | 724 | if (progress < 0) 725 | progress = 0; 726 | else if (progress > 1) 727 | progress = 1; 728 | 729 | [self.layerBack setTransform:[self flipTransform2:progress]]; 730 | [self.layerBackShadow setOpacity:[self flippingPageShadowOpacity] * (1- progress)]; 731 | CGFloat sinOpacity = sin(mp_radians(90 * progress)) * [self coveredPageShadowOpacity]; 732 | [self.layerFacingShadow setOpacity:sinOpacity]; 733 | 734 | [CATransaction commit]; 735 | } 736 | 737 | // fetch the flipping page transform for any position within the 1st half of the animation 738 | // progress ranges from 0 (start) to 1 (complete) 739 | - (CATransform3D)flipTransform1:(CGFloat)progress 740 | { 741 | CATransform3D tHalf1 = CATransform3DIdentity; 742 | 743 | // rotate away from viewer 744 | BOOL forwards = ([self style] & MPFlipStyleDirectionMask) != MPFlipStyleDirectionBackward; 745 | BOOL vertical = ([self style] & MPFlipStyleOrientationMask) == MPFlipStyleOrientationVertical; 746 | tHalf1 = CATransform3DRotate(tHalf1, mp_radians(90 * progress * (forwards? -1 : 1)), vertical? -1 : 0, vertical? 0 : 1, 0); 747 | 748 | return tHalf1; 749 | } 750 | 751 | // fetch the flipping page transform for any position within the 2nd half of the animation 752 | // progress ranges from 0 (start) to 1 (complete) 753 | - (CATransform3D)flipTransform2:(CGFloat)progress 754 | { 755 | CATransform3D tHalf2 = CATransform3DIdentity; 756 | 757 | // rotate away from viewer 758 | BOOL forwards = ([self style] & MPFlipStyleDirectionMask) != MPFlipStyleDirectionBackward; 759 | BOOL vertical = ([self style] & MPFlipStyleOrientationMask) == MPFlipStyleOrientationVertical; 760 | tHalf2 = CATransform3DRotate(tHalf2, mp_radians(90 * (1 - progress)) * (forwards? 1 : -1), vertical? -1 : 0, vertical? 0 : 1, 0); 761 | 762 | return tHalf2; 763 | } 764 | 765 | - (void)animateFromProgress:(CGFloat)fromProgress shouldFallBack:(BOOL)shouldFallBack completion:(void (^)(BOOL finished))completion 766 | { 767 | BOOL isFrontPage = [self stage] == MPFlipAnimationStage1; 768 | if (shouldFallBack == isFrontPage) { 769 | // we're either on 1st page falling back, or on 2nd page falling forward - simple 1 stage animation 770 | [self animateFlip2:shouldFallBack fromProgress:fromProgress withCompletion:completion]; 771 | } 772 | else 773 | { 774 | // we're either on 1st page flipping forward or on 2nd page flipping back - 2 stage animation 775 | [self animateFlip1:shouldFallBack fromProgress:fromProgress withCompletion:completion]; 776 | } 777 | } 778 | 779 | - (void)transitionDidComplete 780 | { 781 | [self transitionDidComplete:YES]; 782 | } 783 | 784 | - (void)transitionDidComplete:(BOOL)completed 785 | { 786 | if (completed) 787 | { 788 | switch (self.completionAction) { 789 | case MPTransitionActionAddRemove: 790 | [self.sourceView removeFromSuperview]; 791 | break; 792 | 793 | case MPTransitionActionShowHide: 794 | [self.sourceView setHidden:YES]; 795 | break; 796 | 797 | case MPTransitionActionNone: 798 | // undo whatever actions we took during animation 799 | if ([self wasDestinationViewShown]) 800 | [self.destinationView setHidden:YES]; 801 | break; 802 | } 803 | } 804 | else { 805 | switch (self.completionAction) { 806 | case MPTransitionActionAddRemove: 807 | [self.destinationView removeFromSuperview]; 808 | break; 809 | 810 | case MPTransitionActionShowHide: 811 | [self.destinationView setHidden:YES]; 812 | break; 813 | 814 | case MPTransitionActionNone: 815 | // undo whatever actions we took during animation 816 | if ([self wasDestinationViewShown]) 817 | [self.destinationView setHidden:YES]; 818 | break; 819 | } 820 | } 821 | } 822 | 823 | #pragma mark - KVO 824 | 825 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 826 | { 827 | if ([keyPath isEqualToString:@"frame"]) 828 | { 829 | if (!CGRectEqualToRect(self.sourceFrame, [self.actingSource frame])) 830 | { 831 | self.rect = self.sourceView.bounds; 832 | [self buildLayers:YES]; 833 | self.sourceFrame = [self.actingSource frame]; 834 | } 835 | } 836 | } 837 | 838 | #pragma mark - Class methods 839 | 840 | + (void)transitionFromViewController:(UIViewController *)fromController toViewController:(UIViewController *)toController duration:(NSTimeInterval)duration style:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion 841 | { 842 | MPFlipTransition *flipTransition = [[MPFlipTransition alloc] initWithSourceView:fromController.view destinationView:toController.view duration:duration style:style completionAction:MPTransitionActionNone]; 843 | [flipTransition perform:completion]; 844 | } 845 | 846 | + (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration style:(MPFlipStyle)style transitionAction:(MPTransitionAction)action completion:(void (^)(BOOL finished))completion 847 | { 848 | MPFlipTransition *flipTransition = [[MPFlipTransition alloc] initWithSourceView:fromView destinationView:toView duration:duration style:style completionAction:action]; 849 | [flipTransition perform:completion]; 850 | } 851 | 852 | + (void)presentViewController:(UIViewController *)viewControllerToPresent from:(UIViewController *)presentingController duration:(NSTimeInterval)duration style:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion 853 | { 854 | MPFlipTransition *flipTransition = [[MPFlipTransition alloc] initWithSourceView:presentingController.view destinationView:viewControllerToPresent.view duration:duration style:style completionAction:MPTransitionActionNone]; 855 | 856 | [flipTransition setPresentingController:presentingController]; 857 | 858 | [flipTransition perform:^(BOOL finished) { 859 | // under iPad for our fold transition, we need to be full screen modal (iPhone is always full screen modal) 860 | UIModalPresentationStyle oldStyle = [presentingController modalPresentationStyle]; 861 | if (oldStyle != UIModalPresentationFullScreen && [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 862 | [presentingController setModalPresentationStyle:UIModalPresentationFullScreen]; 863 | 864 | [presentingController presentViewController:viewControllerToPresent animated:NO completion:^{ 865 | // restore previous modal presentation style 866 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 867 | [presentingController setModalPresentationStyle:oldStyle]; 868 | }]; 869 | 870 | if (completion) 871 | completion(YES); 872 | }]; 873 | } 874 | 875 | + (void)dismissViewControllerFromPresentingController:(UIViewController *)presentingController duration:(NSTimeInterval)duration style:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion 876 | { 877 | UIViewController *src = [presentingController presentedViewController]; 878 | if (!src) 879 | [NSException raise:@"Invalid Operation" format:@"dismissViewControllerFromPresentingController:direction:completion: can only be performed on a view controller with a presentedViewController."]; 880 | 881 | UIViewController *dest = (UIViewController *)presentingController; 882 | 883 | // find out the presentation context for the presenting view controller 884 | while (YES)// (![src definesPresentationContext]) 885 | { 886 | if (![dest parentViewController]) 887 | break; 888 | 889 | dest = [dest parentViewController]; 890 | } 891 | 892 | MPFlipTransition *flipTransition = [[MPFlipTransition alloc] initWithSourceView:src.view destinationView:dest.view duration:duration style:style completionAction:MPTransitionActionNone]; 893 | [flipTransition setDismissing:YES]; 894 | [presentingController dismissViewControllerAnimated:NO completion:nil]; 895 | [flipTransition perform:^(BOOL finished) { 896 | [dest.view setHidden:NO]; 897 | if (completion) 898 | completion(YES); 899 | }]; 900 | } 901 | 902 | @end 903 | 904 | #pragma mark - UIViewController(MPFlipTransition) 905 | 906 | @implementation UIViewController(MPFlipTransition) 907 | 908 | - (void)presentViewController:(UIViewController *)viewControllerToPresent flipStyle:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion 909 | { 910 | [MPFlipTransition presentViewController:viewControllerToPresent from:self duration:[MPFlipTransition defaultDuration] style:style completion:completion]; 911 | } 912 | 913 | - (void)dismissViewControllerWithFlipStyle:(MPFlipStyle)style completion:(void (^)(BOOL finished))completion 914 | { 915 | [MPFlipTransition dismissViewControllerFromPresentingController:self duration:[MPFlipTransition defaultDuration] style:style completion:completion]; 916 | } 917 | 918 | @end 919 | 920 | #pragma mark - UINavigationController(MPFlipTransition) 921 | 922 | @implementation UINavigationController(MPFlipTransition) 923 | 924 | //- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated 925 | - (void)pushViewController:(UIViewController *)viewController flipStyle:(MPFlipStyle)style 926 | { 927 | [MPFlipTransition transitionFromViewController:[self visibleViewController] 928 | toViewController:viewController 929 | duration:[MPFlipTransition defaultDuration] 930 | style:style 931 | completion:^(BOOL finished) { 932 | [self pushViewController:viewController animated:NO]; 933 | } 934 | ]; 935 | } 936 | 937 | - (UIViewController *)popViewControllerWithFlipStyle:(MPFlipStyle)style 938 | { 939 | UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2]; 940 | 941 | [MPFlipTransition transitionFromViewController:[self visibleViewController] 942 | toViewController:toController 943 | duration:[MPFlipTransition defaultDuration] 944 | style:style 945 | completion:^(BOOL finished) { 946 | [self popViewControllerAnimated:NO]; 947 | } 948 | ]; 949 | 950 | return toController; 951 | } 952 | 953 | @end 954 | -------------------------------------------------------------------------------- /Container/MPFlipViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MPFlipViewController.h 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/4/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MPTransitionEnumerations.h" 11 | 12 | enum { 13 | MPFlipViewControllerOrientationHorizontal = 0, 14 | MPFlipViewControllerOrientationVertical = 1 15 | }; 16 | typedef NSInteger MPFlipViewControllerOrientation; 17 | 18 | /*enum { 19 | MPFlipViewControllerSpineLocationNone = 0, // Undefined 20 | MPFlipViewControllerSpineLocationMin = 1, // Spine is at Left or Top 21 | MPFlipViewControllerSpineLocationMid = 2, // Spine is in middle 22 | MPFlipViewControllerSpineLocationMax = 3 // Spine is at Right or Bottom 23 | }; 24 | typedef NSInteger MPFlipViewControllerSpineLocation;*/ 25 | 26 | enum { 27 | MPFlipViewControllerDirectionForward, 28 | MPFlipViewControllerDirectionReverse 29 | }; 30 | typedef NSInteger MPFlipViewControllerDirection; // For 'MPFlipViewControllerOrientationHorizontal', 'forward' is right-to-left, like pages in a book. For 'MPFlipViewControllerOrientationVertical', bottom-to-top, like pages in a wall calendar. 31 | 32 | @protocol MPFlipViewControllerDelegate, MPFlipViewControllerDataSource; 33 | 34 | @interface MPFlipViewController : UIViewController 35 | 36 | @property (nonatomic, readonly) MPFlipViewControllerOrientation orientation; // horizontal or vertical 37 | @property (nonatomic, readonly) UIViewController *viewController; 38 | @property (nonatomic, readonly) NSArray *gestureRecognizers; 39 | @property (nonatomic, assign) id delegate; 40 | @property (nonatomic, assign) id dataSource; // If nil, user gesture-driven navigation will be disabled. 41 | 42 | // designated initializer 43 | - (id)initWithOrientation:(MPFlipViewControllerOrientation)orientation; 44 | 45 | // flip to a new page 46 | - (void)setViewController:(UIViewController *)viewController direction:(MPFlipViewControllerDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL finished))completion; 47 | 48 | @end 49 | 50 | @protocol MPFlipViewControllerDelegate 51 | 52 | @optional 53 | // handle this to be notified when page flip animations have finished 54 | - (void)flipViewController:(MPFlipViewController *)flipViewController didFinishAnimating:(BOOL)finished previousViewController:(UIViewController *)previousViewController transitionCompleted:(BOOL)completed; 55 | 56 | // handle this and return the desired orientation (horizontal or vertical) for the new interface orientation 57 | // called when MPFlipViewController handles willRotateToInterfaceOrientation:duration: callback 58 | - (MPFlipViewControllerOrientation)flipViewController:(MPFlipViewController *)flipViewController orientationForInterfaceOrientation:(UIInterfaceOrientation)orientation; 59 | 60 | @end 61 | 62 | @protocol MPFlipViewControllerDataSource 63 | @required 64 | 65 | - (UIViewController *)flipViewController:(MPFlipViewController *)flipViewController viewControllerBeforeViewController:(UIViewController *)viewController; // get previous page, or nil for none 66 | - (UIViewController *)flipViewController:(MPFlipViewController *)flipViewController viewControllerAfterViewController:(UIViewController *)viewController; // get next page, or nil for none 67 | 68 | @end 69 | 70 | // Notifications 71 | // All of the following notifications have an `object' that is the sending MPFipViewController. 72 | 73 | // The following notification has a userInfo key "MPAnimationFinished" with an NSNumber (bool, YES/NO) value, 74 | // an "MPTransitionCompleted" key with an NSNumber (bool, YES/NO) value, 75 | // an "MPPreviousController" key with a UIViewController value, and 76 | // an "MPNewController" key with a UIViewController value (will be NSNull for rubber-banding past first/last controller) 77 | #define MPAnimationFinishedKey @"MPAnimationFinished" 78 | #define MPTransitionCompletedKey @"MPTransitionCompleted" 79 | #define MPPreviousControllerKey @"MPPreviousController" 80 | #define MPNewControllerKey @"MPNewController" 81 | extern NSString *MPFlipViewControllerDidFinishAnimatingNotification; 82 | 83 | -------------------------------------------------------------------------------- /Container/MPFlipViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MPFlipViewController.m 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/4/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import "MPFlipViewController.h" 10 | #import "MPFlipTransition.h" 11 | 12 | #define MARGIN 44 13 | #define SWIPE_THRESHOLD 125.0f 14 | #define SWIPE_ESCAPE_VELOCITY 650.0f 15 | 16 | // Notifications 17 | NSString *MPFlipViewControllerDidFinishAnimatingNotification = @"com.markpospesel.MPFlipViewControllerDidFinishAnimatingNotification"; 18 | 19 | @interface MPFlipViewController () 20 | 21 | @property (nonatomic, assign) MPFlipViewControllerOrientation orientation; 22 | @property (nonatomic, strong) UIViewController *childViewController; 23 | @property (nonatomic, strong) UIViewController *sourceController; 24 | @property (nonatomic, strong) UIViewController *destinationController; 25 | @property (nonatomic, assign) NSArray *gestureRecognizers; 26 | @property (nonatomic, assign) BOOL gesturesAdded; 27 | @property (nonatomic, readonly) BOOL isAnimating; 28 | @property (nonatomic, assign, getter = isGestureDriven) BOOL gestureDriven; 29 | @property (nonatomic, assign, getter = isPanning) BOOL panning; 30 | @property (nonatomic, assign, getter = isRubberbanding) BOOL rubberbanding; 31 | @property (nonatomic, strong) MPFlipTransition *flipTransition; 32 | @property (assign, nonatomic) CGPoint panStart; 33 | @property (assign, nonatomic) CGPoint lastPanPosition; 34 | @property (assign, nonatomic) BOOL animationDidStartAsPan; 35 | @property (nonatomic, assign) MPFlipViewControllerDirection direction; 36 | 37 | @end 38 | 39 | @implementation MPFlipViewController 40 | 41 | @synthesize delegate = _delegate; 42 | @synthesize dataSource = _dataSource; 43 | 44 | @synthesize orientation = _orientation; 45 | @synthesize childViewController = _childViewController; 46 | @synthesize gestureRecognizers = _gestureRecognizers; 47 | @synthesize gesturesAdded = _gesturesAdded; 48 | @synthesize gestureDriven = _gestureDriven; 49 | @synthesize panning = _panning; 50 | @synthesize rubberbanding = _rubberbanding; 51 | @synthesize flipTransition = _flipTransition; 52 | @synthesize panStart = _panStart; 53 | @synthesize lastPanPosition = _lastPanPosition; 54 | @synthesize animationDidStartAsPan = _animationDidStartAsPan; 55 | @synthesize direction = _direction; 56 | @synthesize sourceController = _sourceController; 57 | @synthesize destinationController = _destinationController; 58 | 59 | - (id)initWithOrientation:(MPFlipViewControllerOrientation)orientation 60 | { 61 | self = [super init]; 62 | if (self) { 63 | // Custom initialization 64 | _orientation = orientation; 65 | _direction = MPFlipViewControllerDirectionForward; 66 | _gesturesAdded = NO; 67 | _panning = NO; 68 | _gestureDriven = NO; 69 | _rubberbanding = NO; 70 | } 71 | return self; 72 | } 73 | 74 | - (void)viewDidLoad 75 | { 76 | [super viewDidLoad]; 77 | // Do any additional setup after loading the view. 78 | 79 | [self addGestures]; 80 | } 81 | 82 | - (void)viewDidUnload 83 | { 84 | [super viewDidUnload]; 85 | // Release any retained subviews of the main view. 86 | } 87 | 88 | #pragma mark - rotation callbacks 89 | 90 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 91 | { 92 | return ![self isAnimating]; 93 | } 94 | 95 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 96 | { 97 | if ([[self delegate] respondsToSelector:@selector(flipViewController:orientationForInterfaceOrientation:)]) 98 | [self setOrientation:[[self delegate] flipViewController:self orientationForInterfaceOrientation:toInterfaceOrientation]]; 99 | 100 | [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; 101 | } 102 | 103 | #pragma mark - Properties 104 | 105 | - (UIViewController *)viewController 106 | { 107 | return [self childViewController]; 108 | } 109 | 110 | - (BOOL)isAnimating 111 | { 112 | return [self flipTransition] != nil; 113 | } 114 | 115 | - (BOOL)isFlipFrontPage 116 | { 117 | return [[self flipTransition] stage] == MPFlipAnimationStage1; 118 | } 119 | 120 | - (void)setPanning:(BOOL)panning 121 | { 122 | if (_panning != panning) 123 | { 124 | _panning = panning; 125 | if (panning) 126 | { 127 | [self setAnimationDidStartAsPan:YES]; 128 | } 129 | } 130 | } 131 | 132 | #pragma mark - private instance methods 133 | 134 | - (void)addGestures 135 | { 136 | if ([self gesturesAdded]) 137 | return; 138 | 139 | // Add our swipe gestures 140 | BOOL isHorizontal = ([self orientation] == MPFlipViewControllerOrientationHorizontal); 141 | UISwipeGestureRecognizer *left = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeNext:)]; 142 | left.direction = isHorizontal? UISwipeGestureRecognizerDirectionLeft : UISwipeGestureRecognizerDirectionUp; 143 | left.delegate = self; 144 | [self.view addGestureRecognizer:left]; 145 | 146 | UISwipeGestureRecognizer *right = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipePrev:)]; 147 | right.direction = isHorizontal? UISwipeGestureRecognizerDirectionRight : UISwipeGestureRecognizerDirectionDown; 148 | right.delegate = self; 149 | [self.view addGestureRecognizer:right]; 150 | 151 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; 152 | tap.delegate = self; 153 | [self.view addGestureRecognizer:tap]; 154 | 155 | UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; 156 | pan.delegate = self; 157 | [self.view addGestureRecognizer:pan]; 158 | 159 | self.gestureRecognizers = [NSArray arrayWithObjects:left, right, tap, pan, nil]; 160 | 161 | [self setGesturesAdded:YES]; 162 | } 163 | 164 | #pragma mark - public Instance methods 165 | 166 | - (void)setViewController:(UIViewController *)viewController direction:(MPFlipViewControllerDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL finished))completion 167 | { 168 | UIViewController *previousController = [self viewController]; 169 | 170 | BOOL isForward = (direction == MPFlipViewControllerDirectionForward); 171 | [[viewController view] setFrame:[self.view bounds]]; 172 | [self addChildViewController:viewController]; // this calls [viewController willMoveToParentViewController:self] for us 173 | [self setChildViewController:viewController]; 174 | [previousController willMoveToParentViewController:nil]; 175 | 176 | if (animated && previousController) 177 | { 178 | [self startFlipToViewController:viewController 179 | fromViewController:previousController 180 | withDirection:(isForward? MPFlipStyleDefault : MPFlipStyleDirectionBackward)]; 181 | 182 | [self.flipTransition perform:^(BOOL finished) { 183 | [self endFlipAnimation:finished transitionCompleted:YES completion:completion]; 184 | }]; 185 | } 186 | else 187 | { 188 | [[self view] addSubview:[viewController view]]; 189 | [[previousController view] removeFromSuperview]; 190 | [viewController didMoveToParentViewController:self]; 191 | if (completion) 192 | completion(YES); 193 | [previousController removeFromParentViewController]; // this calls [previousController didMoveToParentViewController:nil] for us 194 | } 195 | } 196 | 197 | #pragma mark - Gesture handlers 198 | 199 | - (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer 200 | { 201 | if ([self isAnimating]) 202 | return; 203 | 204 | CGPoint tapPoint = [gestureRecognizer locationInView:self.view]; 205 | BOOL isHorizontal = [self orientation] == MPFlipViewControllerOrientationHorizontal; 206 | CGFloat value = isHorizontal? tapPoint.x : tapPoint.y; 207 | CGFloat dimension = isHorizontal? self.view.bounds.size.width : self.view.bounds.size.height; 208 | NSLog(@"Tap to flip"); 209 | if (value <= MARGIN) 210 | [self gotoPreviousPage]; 211 | else if (value >= dimension - MARGIN) 212 | [self gotoNextPage]; 213 | } 214 | 215 | - (void)handleSwipePrev:(UIGestureRecognizer *)gestureRecognizer 216 | { 217 | if ([self isAnimating]) 218 | return; 219 | 220 | NSLog(@"Swipe to previous page"); 221 | [self gotoPreviousPage]; 222 | } 223 | 224 | - (void)handleSwipeNext:(UIGestureRecognizer *)gestureRecognizer 225 | { 226 | if ([self isAnimating]) 227 | return; 228 | 229 | NSLog(@"Swipe to next page"); 230 | [self gotoNextPage]; 231 | } 232 | 233 | - (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer 234 | { 235 | UIGestureRecognizerState state = [gestureRecognizer state]; 236 | CGPoint currentPosition = [gestureRecognizer locationInView:self.view]; 237 | 238 | if (state == UIGestureRecognizerStateBegan) 239 | { 240 | if ([self isAnimating]) 241 | return; 242 | 243 | // See if touch started near one of the edges, in which case we'll pan a page turn 244 | BOOL isHorizontal = [self orientation] == MPFlipViewControllerOrientationHorizontal; 245 | CGFloat value = isHorizontal? currentPosition.x : currentPosition.y; 246 | CGFloat dimension = isHorizontal? self.view.bounds.size.width : self.view.bounds.size.height; 247 | if (value <= MARGIN) 248 | { 249 | if (![self startFlipWithDirection:MPFlipViewControllerDirectionReverse]) 250 | return; 251 | } 252 | else if (value >= dimension - MARGIN) 253 | { 254 | if (![self startFlipWithDirection:MPFlipViewControllerDirectionForward]) 255 | return; 256 | } 257 | else 258 | { 259 | // Do nothing for now, but it might become a swipe later 260 | return; 261 | } 262 | 263 | [self setPanning:YES]; 264 | [self setPanStart:currentPosition]; 265 | [self setLastPanPosition:currentPosition]; 266 | } 267 | 268 | if ([self isPanning] && state == UIGestureRecognizerStateChanged) 269 | { 270 | CGFloat progress = [self progressFromPosition:currentPosition]; 271 | CGPoint vel = [gestureRecognizer velocityInView:gestureRecognizer.view]; 272 | //NSLog(@"Pan position changed, velocity = %@", NSStringFromCGPoint(vel)); 273 | CGFloat velocityComponent = (self.orientation == MPFlipViewControllerOrientationHorizontal)? vel.x : vel.y; 274 | CGFloat velocityMinorComponent = (self.orientation == MPFlipViewControllerOrientationHorizontal)? vel.y : vel.x; 275 | // ignore the velocity if it's mostly in the off-axis direction (e.g. don't consider left velocity if swipe is mostly up or even diagonally up-left) 276 | if (fabs(velocityMinorComponent) > fabs(velocityComponent)) 277 | velocityComponent = 0; 278 | 279 | if (![self isRubberbanding] && (velocityComponent < -SWIPE_ESCAPE_VELOCITY || velocityComponent > SWIPE_ESCAPE_VELOCITY)) 280 | { 281 | // Detected a swipe to the left 282 | NSLog(@"Escape velocity reached."); 283 | BOOL shouldFallBack = (velocityComponent < -SWIPE_ESCAPE_VELOCITY)? self.direction != MPFlipViewControllerDirectionForward : self.direction == MPFlipViewControllerDirectionForward; 284 | [self setPanning:NO]; 285 | 286 | // finish the remaining animation, but from the last touch position 287 | [self finishPan:shouldFallBack]; 288 | } 289 | else 290 | { 291 | if (progress < 1) 292 | [self.flipTransition setStage:MPFlipAnimationStage1 progress:progress]; 293 | else 294 | [self.flipTransition setStage:MPFlipAnimationStage2 progress:progress - 1]; 295 | [self setLastPanPosition:currentPosition]; 296 | } 297 | } 298 | 299 | if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled) 300 | { 301 | CGPoint vel = [gestureRecognizer velocityInView:gestureRecognizer.view]; 302 | CGFloat velocityComponent = (self.orientation == MPFlipViewControllerOrientationHorizontal)? vel.x : vel.y; 303 | CGFloat velocityMinorComponent = (self.orientation == MPFlipViewControllerOrientationHorizontal)? vel.y : vel.x; 304 | // ignore the velocity if it's mostly in the off-axis direction (e.g. don't consider left velocity if swipe is mostly up or even diagonally up-left) 305 | if (fabs(velocityMinorComponent) > fabs(velocityComponent)) 306 | velocityComponent = 0; 307 | 308 | //NSLog(@"Terminal velocity = %@", NSStringFromCGPoint(vel)); 309 | if ([self isPanning]) 310 | { 311 | // If moving slowly, let page fall either forward or back depending on where we were 312 | BOOL shouldFallBack = [self isFlipFrontPage]; 313 | 314 | if ([self isRubberbanding]) 315 | shouldFallBack = YES; 316 | // But, if user was swiping in an appropriate direction, go ahead and honor that 317 | else if (velocityComponent < -SWIPE_THRESHOLD) 318 | { 319 | // Detected a swipe to the left/top 320 | shouldFallBack = self.direction != MPFlipViewControllerDirectionForward; 321 | } 322 | else if (velocityComponent > SWIPE_THRESHOLD) 323 | { 324 | // Detected a swipe to the right/bottom 325 | shouldFallBack = self.direction == MPFlipViewControllerDirectionForward; 326 | } 327 | 328 | // finish Animation 329 | [self finishPan:shouldFallBack]; 330 | } 331 | } 332 | } 333 | 334 | #pragma mark - UIGestureRecognizerDelegate 335 | 336 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 337 | { 338 | // don't recognize any further gestures if we're in the middle of animating a page-turn 339 | if ([self isAnimating]) 340 | return NO; 341 | 342 | if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] || [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) 343 | { 344 | // for taps and pans, only handle if started within margin, otherwise don't receive so that the content may handle it 345 | CGPoint tapPoint = [touch locationInView:self.view]; 346 | BOOL isHorizontal = [self orientation] == MPFlipViewControllerOrientationHorizontal; 347 | CGFloat value = isHorizontal? tapPoint.x : tapPoint.y; 348 | CGFloat dimension = isHorizontal? self.view.bounds.size.width : self.view.bounds.size.height; 349 | return (value <= MARGIN || value >= dimension - MARGIN); 350 | } 351 | 352 | return YES; 353 | } 354 | 355 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 356 | { 357 | // don't recognize simultaneously with scroll view gestures in content area 358 | if ([[otherGestureRecognizer view] isKindOfClass:[UIScrollView class]]) 359 | return NO; 360 | 361 | // Allow simultanoues pan & swipe recognizers 362 | return YES; 363 | } 364 | 365 | #pragma mark - Private instance methods 366 | 367 | - (CGFloat)progressFromPosition:(CGPoint)position 368 | { 369 | // Determine where we are in our page turn animation 370 | // 0 - 1 means flipping the front-side of the page 371 | // 1 - 2 means flipping the back-side of the page 372 | BOOL isForward = ([self direction] == MPFlipViewControllerDirectionForward); 373 | BOOL isVertical = ([self orientation] == MPFlipViewControllerOrientationVertical); 374 | 375 | CGFloat positionValue = isVertical? position.y : position.x; 376 | CGFloat startValue = isVertical? self.panStart.y : self.panStart.x; 377 | CGFloat dimensionValue = isVertical? self.view.frame.size.height : self.view.frame.size.width; 378 | CGFloat difference = positionValue - startValue; 379 | CGFloat halfWidth = fabsf(startValue - (dimensionValue / 2)); 380 | CGFloat progress = difference / halfWidth * (isForward? - 1 : 1); 381 | if ([self isRubberbanding]) 382 | { 383 | if ((difference > 0) == isForward) 384 | progress = 0; 385 | else 386 | { 387 | // version of Hill equation (AKA Langmuir absorption equation), y = Kx^n / (1 + Kx^n) 388 | // basically I want it to get increasingly more difficult to pull the page until we reach a maximum progress of 0.667 389 | halfWidth += MAX(halfWidth * 2, halfWidth + (dimensionValue / 2)); 390 | CGFloat K = 1/(halfWidth * 3); // K & n can be adjusted to get different reaction curves 391 | CGFloat n = 1.6667; 392 | CGFloat temp = K * powf(fabs(difference), n); 393 | progress = 0.667 * (temp/ (1 + temp)); // scale it to never get past 0.6667 (normally it is asymptotic to 1) 394 | } 395 | } 396 | 397 | //NSLog(@"Difference = %.2f, Half width = %.2f, rawProgress = %.4f", difference, halfWidth, progress); 398 | if (progress < 0) 399 | progress = 0; 400 | if (progress > 2) 401 | progress = 2; 402 | return progress; 403 | } 404 | 405 | - (void)finishPan:(BOOL)shouldFallBack 406 | { 407 | // finishAnimation 408 | CGFloat fromProgress = [self progressFromPosition:[self lastPanPosition]]; 409 | if (shouldFallBack != [self isFlipFrontPage]) 410 | { 411 | // 2-stage animation (we're swiping either forward or back) 412 | if (([self isFlipFrontPage] && fromProgress > 1) || (![self isFlipFrontPage] && fromProgress < 1)) 413 | fromProgress = 1; 414 | if (fromProgress > 1) 415 | fromProgress -= 1; 416 | } 417 | else 418 | { 419 | // 1-stage animation 420 | if (!shouldFallBack) 421 | fromProgress -= 1; 422 | } 423 | [[self flipTransition] animateFromProgress:fromProgress shouldFallBack:shouldFallBack completion:^(BOOL finished) { 424 | [self endFlipAnimation:finished transitionCompleted:!shouldFallBack completion:nil]; 425 | }]; 426 | } 427 | 428 | - (BOOL)startFlipWithDirection:(MPFlipViewControllerDirection)direction 429 | { 430 | if (![self dataSource]) 431 | return NO; 432 | 433 | UIViewController *destinationController = (direction == MPFlipViewControllerDirectionForward)? 434 | [[self dataSource] flipViewController:self viewControllerAfterViewController:[self viewController]] : 435 | [[self dataSource] flipViewController:self viewControllerBeforeViewController:[self viewController]]; 436 | 437 | if (!destinationController) 438 | { 439 | // we're at first or last page, but allow user to lift up current page a bit, 440 | // so we'll pass in a dummy blank page to show behind 441 | [self setRubberbanding:YES]; 442 | } 443 | 444 | [self setGestureDriven:YES]; 445 | [self startFlipToViewController:destinationController fromViewController:[self viewController] withDirection:direction]; 446 | 447 | return YES; 448 | } 449 | 450 | - (void)startFlipToViewController:(UIViewController *)destinationController fromViewController:(UIViewController *)sourceController withDirection:(MPFlipViewControllerDirection)direction 451 | { 452 | BOOL isForward = (direction == MPFlipViewControllerDirectionForward); 453 | BOOL isVertical = ([self orientation] == MPFlipViewControllerOrientationVertical); 454 | [self setSourceController:sourceController]; 455 | [self setDestinationController:destinationController]; 456 | [self setDirection:direction]; 457 | self.flipTransition = [[MPFlipTransition alloc] initWithSourceView:[sourceController view] 458 | destinationView:[destinationController view] 459 | duration:0.5 460 | style:((isForward? MPFlipStyleDefault : MPFlipStyleDirectionBackward) | (isVertical? MPFlipStyleOrientationVertical : MPFlipStyleDefault)) 461 | completionAction:MPTransitionActionAddRemove]; 462 | 463 | [self.flipTransition buildLayers]; 464 | 465 | // set the back page in the vertical position (midpoint of animation) 466 | [self.flipTransition prepareForStage2]; 467 | } 468 | 469 | - (void)endFlipAnimation:(BOOL)animationFinished transitionCompleted:(BOOL)transitionCompleted completion:(void (^)(BOOL finished))completion 470 | { 471 | BOOL didStartAsPan = [self animationDidStartAsPan]; 472 | // clear some flags 473 | [self setFlipTransition:nil]; 474 | [self setPanning:NO]; 475 | [self setAnimationDidStartAsPan:NO]; 476 | 477 | if (transitionCompleted) 478 | { 479 | // If page turn was completed, then we need to send our various notifications as per the Containment API 480 | if (didStartAsPan) 481 | { 482 | // these weren't sent at beginning (because we couldn't know beforehand 483 | // whether the gesture would result in a page turn or not) 484 | [self addChildViewController:self.destinationController]; // this calls [self.destinationController willMoveToParentViewController:self] for us 485 | [self setChildViewController:self.destinationController]; 486 | [self.sourceController willMoveToParentViewController:nil]; 487 | } 488 | 489 | // final set of containment notifications 490 | [self.destinationController didMoveToParentViewController:self]; 491 | [self.sourceController removeFromParentViewController]; // this calls [self.sourceController didMoveToParentViewController:nil] for us 492 | } 493 | 494 | if (completion) 495 | completion(animationFinished); 496 | 497 | if ([self isGestureDriven]) 498 | { 499 | // notify delegate that we finished the page turn animation, indicating whether the user actually completed the page turn 500 | // or not, and also whether the animation ran to completion or not 501 | if ([[self delegate] respondsToSelector:@selector(flipViewController:didFinishAnimating:previousViewController:transitionCompleted:)]) 502 | { 503 | [[self delegate] flipViewController:self didFinishAnimating:animationFinished previousViewController:self.sourceController transitionCompleted:transitionCompleted]; 504 | } 505 | 506 | // Send notification. 507 | id previousController = self.sourceController? self.sourceController : [NSNull null]; 508 | id newController = self.destinationController? self.destinationController : [NSNull null]; 509 | NSDictionary *info = [NSDictionary dictionaryWithObjects: 510 | [NSArray arrayWithObjects:[NSNumber numberWithBool:animationFinished], [NSNumber numberWithBool:transitionCompleted], previousController, newController, nil] 511 | forKeys: 512 | [NSArray arrayWithObjects:MPAnimationFinishedKey, MPTransitionCompletedKey, MPPreviousControllerKey, MPNewControllerKey, nil]]; 513 | [[NSNotificationCenter defaultCenter] postNotificationName:MPFlipViewControllerDidFinishAnimatingNotification 514 | object:self 515 | userInfo:info]; 516 | } 517 | 518 | // clear remaining flags 519 | self.sourceController = nil; 520 | self.destinationController = nil; 521 | [self setGestureDriven:NO]; 522 | [self setRubberbanding:NO]; 523 | } 524 | 525 | - (void)gotoPreviousPage 526 | { 527 | if (![self dataSource]) 528 | return; 529 | 530 | UIViewController *previousController = [[self dataSource] flipViewController:self viewControllerBeforeViewController:[self viewController]]; 531 | if (!previousController) 532 | { 533 | [self setRubberbanding:YES]; 534 | [self startFlipToViewController:nil fromViewController:self.childViewController withDirection:MPFlipViewControllerDirectionReverse]; 535 | [self.flipTransition performRubberband:^(BOOL finished) { 536 | [self endFlipAnimation:finished transitionCompleted:NO completion:nil]; 537 | }]; 538 | return; 539 | } 540 | 541 | [self setGestureDriven:YES]; 542 | [self setViewController:previousController direction:MPFlipViewControllerDirectionReverse animated:YES completion:nil]; 543 | } 544 | 545 | - (void)gotoNextPage 546 | { 547 | if (![self dataSource]) 548 | return; 549 | 550 | UIViewController *nextController = [[self dataSource] flipViewController:self viewControllerAfterViewController:[self viewController]]; 551 | if (!nextController) 552 | { 553 | [self setRubberbanding:YES]; 554 | [self startFlipToViewController:nil fromViewController:self.childViewController withDirection:MPFlipViewControllerDirectionForward]; 555 | [self.flipTransition performRubberband:^(BOOL finished) { 556 | [self endFlipAnimation:finished transitionCompleted:NO completion:nil]; 557 | }]; 558 | return; 559 | } 560 | 561 | [self setGestureDriven:YES]; 562 | [self setViewController:nextController direction:MPFlipViewControllerDirectionForward animated:YES completion:nil]; 563 | } 564 | 565 | @end 566 | -------------------------------------------------------------------------------- /Container/MPTransition.h: -------------------------------------------------------------------------------- 1 | // 2 | // MPTransition.h 3 | // MPFoldTransition (v1.1.4) 4 | // 5 | // Created by Mark Pospesel on 5/14/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MPTransitionEnumerations.h" 11 | 12 | typedef void (^CompletionBlock)(BOOL); 13 | 14 | 15 | @interface MPTransition : NSObject 16 | 17 | #pragma mark - Properties 18 | 19 | @property (strong, nonatomic) UIView *sourceView; 20 | @property (strong, nonatomic) UIView *destinationView; 21 | @property (assign, nonatomic) NSTimeInterval duration; 22 | @property (assign, nonatomic) CGRect rect; 23 | @property (assign, nonatomic) MPTransitionAction completionAction; 24 | @property (assign, nonatomic) UIViewAnimationCurve timingCurve; 25 | // Perspective component of transformation (Advanced use only, generally don't need to adjust) 26 | @property (assign, nonatomic) float m34; 27 | 28 | // Special case of dismissing a modal view 29 | @property (assign, nonatomic, getter = isDimissing) BOOL dismissing; 30 | 31 | #pragma mark - init 32 | 33 | - (id)initWithSourceView:(UIView *)sourceView destinationView:(UIView *)destinationView duration:(NSTimeInterval)duration timingCurve:(UIViewAnimationCurve)timingCurve completionAction:(MPTransitionAction)action; 34 | 35 | #pragma mark - Instance methods 36 | 37 | - (void)perform; 38 | - (void)perform:(void (^)(BOOL finished))completion; 39 | 40 | - (NSString *)timingCurveFunctionName; 41 | - (void)transitionDidComplete; 42 | - (void)setPresentingController:(UIViewController *)presentingController; 43 | 44 | #pragma mark - Class methods 45 | 46 | + (NSTimeInterval)defaultDuration; 47 | 48 | @end 49 | 50 | #pragma mark - UIView extensions 51 | 52 | @interface UIView(MPAnimation) 53 | 54 | + (BOOL)subView:(UIView *)subView1 isAboveSubView:(UIView *)subView2; 55 | + (BOOL)subView:(UIView *)subView1 isBelowSubView:(UIView *)subView2; 56 | - (BOOL)isAboveSiblingView:(UIView *)siblingView; 57 | - (BOOL)isBelowSiblingView:(UIView *)siblingView; 58 | 59 | @end -------------------------------------------------------------------------------- /Container/MPTransition.m: -------------------------------------------------------------------------------- 1 | // 2 | // MPTransition.m 3 | // MPFoldTransition (v1.1.4) 4 | // 5 | // Created by Mark Pospesel on 5/14/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #define DEFAULT_DURATION 0.3 10 | 11 | #import "MPTransition.h" 12 | #import 13 | @implementation MPTransition 14 | 15 | @synthesize sourceView = _sourceView; 16 | @synthesize destinationView = _destinationView; 17 | @synthesize duration = _duration; 18 | @synthesize rect = _rect; 19 | @synthesize completionAction = _completionAction; 20 | @synthesize timingCurve = _timingCurve; 21 | @synthesize dismissing = _dismissing; 22 | @synthesize m34 = _m34; 23 | 24 | - (id)initWithSourceView:(UIView *)sourceView destinationView:(UIView *)destinationView duration:(NSTimeInterval)duration timingCurve:(UIViewAnimationCurve)timingCurve completionAction:(MPTransitionAction)action { 25 | self = [super init]; 26 | if (self) 27 | { 28 | _sourceView = sourceView; 29 | _destinationView = destinationView; 30 | _duration = duration; 31 | _rect = [sourceView bounds]; 32 | _timingCurve = timingCurve; 33 | _completionAction = action; 34 | _m34 = INFINITY; 35 | } 36 | 37 | return self; 38 | } 39 | 40 | #pragma mark - Instance methods 41 | 42 | - (NSString *)timingCurveFunctionName 43 | { 44 | switch ([self timingCurve]) { 45 | case UIViewAnimationCurveEaseOut: 46 | return kCAMediaTimingFunctionEaseOut; 47 | 48 | case UIViewAnimationCurveEaseIn: 49 | return kCAMediaTimingFunctionEaseIn; 50 | 51 | case UIViewAnimationCurveEaseInOut: 52 | return kCAMediaTimingFunctionEaseInEaseOut; 53 | 54 | case UIViewAnimationCurveLinear: 55 | return kCAMediaTimingFunctionLinear; 56 | } 57 | 58 | return kCAMediaTimingFunctionDefault; 59 | } 60 | 61 | - (void)perform 62 | { 63 | [self perform:nil]; 64 | } 65 | 66 | - (void)perform:(void (^)(BOOL finished))completion 67 | { 68 | [NSException raise:@"Incomplete Implementation" format:@"MPTransition must be subclassed and the perform: method implemented."]; 69 | } 70 | 71 | - (void)transitionDidComplete 72 | { 73 | switch (self.completionAction) { 74 | case MPTransitionActionAddRemove: 75 | [[self.sourceView superview] addSubview:self.destinationView]; 76 | [self.sourceView removeFromSuperview]; 77 | [self.sourceView setHidden:NO]; 78 | break; 79 | 80 | case MPTransitionActionShowHide: 81 | [self.destinationView setHidden:NO]; 82 | [self.sourceView setHidden:YES]; 83 | break; 84 | 85 | case MPTransitionActionNone: 86 | [self.sourceView setHidden:NO]; 87 | break; 88 | } 89 | } 90 | 91 | - (void)setPresentingController:(UIViewController *)presentingController 92 | { 93 | UIViewController *src = (UIViewController *)presentingController; 94 | // find out the presentation context for the presenting view controller 95 | while (YES)// (![src definesPresentationContext]) 96 | { 97 | if (![src parentViewController]) 98 | break; 99 | 100 | src = [src parentViewController]; 101 | } 102 | 103 | [self setSourceView:src.view]; 104 | if ([src wantsFullScreenLayout]) 105 | { 106 | // don't include the status bar height in the rect to fold 107 | CGRect frame = src.view.frame; 108 | CGRect frameViewRect = [src.view convertRect:frame fromView:nil]; 109 | CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame]; 110 | CGRect statusBarWindowRect = [src.view.window convertRect:statusBarFrame fromWindow: nil]; 111 | CGRect statusBarViewRect = [src.view convertRect:statusBarWindowRect fromView: nil]; 112 | frameViewRect.origin.y += statusBarViewRect.size.height; 113 | frameViewRect.size.height -= statusBarViewRect.size.height; 114 | [self setRect:frameViewRect]; 115 | } 116 | } 117 | 118 | #pragma mark - Class methods 119 | 120 | + (NSTimeInterval)defaultDuration 121 | { 122 | return DEFAULT_DURATION; 123 | } 124 | 125 | @end 126 | 127 | #pragma mark - UIView extensions 128 | 129 | @implementation UIView(MPAnimation) 130 | 131 | + (BOOL)subView:(UIView *)subView1 isAboveSubView:(UIView *)subView2 132 | { 133 | UIView *superview = [subView1 superview]; 134 | int index1 = [[superview subviews] indexOfObject:subView1]; 135 | int index2 = [[superview subviews] indexOfObject:subView2]; 136 | if (index2 == NSNotFound) 137 | [NSException raise:@"Invalid Operation" format:@"Both views must have the same superview"]; 138 | return index1 > index2; 139 | } 140 | 141 | + (BOOL)subView:(UIView *)subView1 isBelowSubView:(UIView *)subView2 142 | { 143 | return [self subView:subView2 isAboveSubView:subView1]; 144 | } 145 | 146 | - (BOOL)isAboveSiblingView:(UIView *)siblingView 147 | { 148 | return [UIView subView:self isAboveSubView:siblingView]; 149 | } 150 | 151 | - (BOOL)isBelowSiblingView:(UIView *)siblingView 152 | { 153 | return [UIView subView:siblingView isAboveSubView:self]; 154 | } 155 | 156 | @end 157 | -------------------------------------------------------------------------------- /Container/MPTransitionEnumerations.h: -------------------------------------------------------------------------------- 1 | // 2 | // MPTransitionEnumerations.h 3 | // MPFoldTransition (v1.0.1) 4 | // 5 | // Created by Mark Pospesel on 5/14/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #ifndef MPFoldTransition_MPTransitionEnumerations_h 10 | #define MPFoldTransition_MPTransitionEnumerations_h 11 | 12 | // Action to take upon completion of the transition 13 | enum { 14 | MPTransitionActionAddRemove, // add/remove subViews upon completion 15 | MPTransitionActionShowHide, // show/hide subViews upon completion 16 | MPTransitionActionNone // take no action (use when container view controller will handle add/remove) 17 | } typedef MPTransitionAction; 18 | 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Default-568h@2x.png -------------------------------------------------------------------------------- /Demo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/4/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/4/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | @synthesize window = _window; 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | // Override point for customization after application launch. 18 | return YES; 19 | } 20 | 21 | - (void)applicationWillResignActive:(UIApplication *)application 22 | { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | - (void)applicationDidEnterBackground:(UIApplication *)application 28 | { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application 34 | { 35 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 36 | } 37 | 38 | - (void)applicationDidBecomeActive:(UIApplication *)application 39 | { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 41 | } 42 | 43 | - (void)applicationWillTerminate:(UIApplication *)application 44 | { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Demo/ContentViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.h 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/5/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ContentViewController : UIViewController 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 14 | 15 | // Empty view that holds the image and description 16 | @property (weak, nonatomic) IBOutlet UIView *contentArea; 17 | 18 | // White border for movie image (to give it a Polaroid look) 19 | @property (weak, nonatomic) IBOutlet UIView *imageFrame; 20 | @property (weak, nonatomic) IBOutlet UIImageView *imageView; 21 | 22 | @property (weak, nonatomic) IBOutlet UITextView *descriptionField; 23 | 24 | // Index of the movie (1 - 3) 25 | @property (assign, nonatomic) NSUInteger movieIndex; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Demo/ContentViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.m 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/5/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import "ContentViewController.h" 10 | #import 11 | 12 | #define FRAME_MARGIN 10 13 | 14 | @interface ContentViewController () 15 | 16 | @property (assign, nonatomic, getter = isRotating) BOOL rotating; 17 | 18 | @end 19 | 20 | @implementation ContentViewController 21 | @synthesize titleLabel = _titleLabel; 22 | @synthesize contentArea = _contentArea; 23 | @synthesize imageFrame = _imageFrame; 24 | @synthesize imageView = _imageView; 25 | @synthesize descriptionField = _descriptionField; 26 | @synthesize movieIndex = _movieIndex; 27 | @synthesize rotating = _rotating; 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | // Do any additional setup after loading the view. 33 | [self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"Pattern - Aged Paper"]]]; 34 | 35 | [self.imageView setImage:[UIImage imageNamed:[NSString stringWithFormat:@"matrix_%02d", [self movieIndex]]]]; 36 | switch ([self movieIndex]) { 37 | case 1: 38 | self.titleLabel.text = @"The Matrix (1999)"; 39 | self.descriptionField.text = @"A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers. Neo and the rebel leaders estimate that they have 72 hours until 250,000 probes discover Zion and destroy it and its inhabitants. During this, Neo must decide how he can save Trinity from a dark fate in his dreams. Neo and the rebel leaders estimate that they have 72 hours until 250,000 probes discover Zion and destroy it and its inhabitants. During this, Neo must decide how he can save Trinity from a dark fate in his dreams."; 40 | break; 41 | 42 | case 2: 43 | self.titleLabel.text = @"The Matrix Reloaded (2003)"; 44 | self.descriptionField.text = @"Neo and the rebel leaders estimate that they have 72 hours until 250,000 probes discover Zion and destroy it and its inhabitants. During this, Neo must decide how he can save Trinity from a dark fate in his dreams. Neo and the rebel leaders estimate that they have 72 hours until 250,000 probes discover Zion and destroy it and its inhabitants. During this, Neo must decide how he can save Trinity from a dark fate in his dreams."; 45 | break; 46 | 47 | case 3: 48 | self.titleLabel.text = @"The Matrix Revolutions (2003)"; 49 | self.descriptionField.text = @"The human city of Zion defends itself against the massive invasion of the machines as Neo fights to end the war at another front while also opposing the rogue Agent Smith. Neo and the rebel leaders estimate that they have 72 hours until 250,000 probes discover Zion and destroy it and its inhabitants. During this, Neo must decide how he can save Trinity from a dark fate in his dreams."; 50 | break; 51 | 52 | default: 53 | break; 54 | } 55 | 56 | [self.imageFrame.layer setShadowOpacity:0.5]; 57 | [self.imageFrame.layer setShadowOffset:CGSizeMake(0, 1)]; 58 | } 59 | 60 | - (void)viewDidUnload 61 | { 62 | [self setTitleLabel:nil]; 63 | [self setImageFrame:nil]; 64 | [self setImageView:nil]; 65 | [self setDescriptionField:nil]; 66 | [self setContentArea:nil]; 67 | [super viewDidUnload]; 68 | // Release any retained subviews of the main view. 69 | } 70 | 71 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 72 | { 73 | return YES; 74 | } 75 | 76 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 77 | { 78 | [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; 79 | 80 | [self setRotating:YES]; 81 | } 82 | 83 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 84 | { 85 | [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; 86 | 87 | [self setShadowPathsWithAnimationDuration:duration]; 88 | } 89 | 90 | - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation 91 | { 92 | [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; 93 | 94 | [self setRotating:NO]; 95 | } 96 | 97 | - (void)viewWillAppear:(BOOL)animated 98 | { 99 | [super viewWillAppear:animated]; 100 | NSLog(@"viewWillAppear"); 101 | } 102 | 103 | - (void)viewDidAppear:(BOOL)animated 104 | { 105 | [super viewDidAppear:animated]; 106 | NSLog(@"viewDidAppear"); 107 | } 108 | 109 | - (void)viewWillDisappear:(BOOL)animated 110 | { 111 | [super viewWillDisappear:animated]; 112 | NSLog(@"viewWillDisappear"); 113 | } 114 | 115 | - (void)viewDidDisappear:(BOOL)animated 116 | { 117 | [super viewDidDisappear:animated]; 118 | NSLog(@"viewDidDisappear"); 119 | } 120 | 121 | - (void)viewWillLayoutSubviews 122 | { 123 | [super viewWillLayoutSubviews]; 124 | 125 | CGRect frame = self.contentArea.frame; 126 | CGFloat maxPictureWidth = frame.size.width - 2 * FRAME_MARGIN; 127 | CGFloat maxPictureHeight = frame.size.height - 2 * FRAME_MARGIN; 128 | CGFloat fitToWidthHeight = maxPictureWidth * (3./4); 129 | CGFloat fitToHeightWidth = maxPictureHeight * (4./3); 130 | 131 | BOOL fitToWidth = fitToHeightWidth > maxPictureWidth; 132 | CGFloat contentGap = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone? 10 : 20; 133 | 134 | if (fitToWidth) 135 | { 136 | if (maxPictureWidth > 480) 137 | { 138 | maxPictureWidth = 480; 139 | fitToWidthHeight = 360; 140 | } 141 | 142 | CGFloat pictureHeightWithFrame = fitToWidthHeight + 2 * FRAME_MARGIN; 143 | CGFloat pictureWidthWithFrame = maxPictureWidth + 2 * FRAME_MARGIN; 144 | 145 | self.imageFrame.frame = CGRectMake((frame.size.width - pictureWidthWithFrame) / 2, 0, pictureWidthWithFrame, pictureHeightWithFrame); 146 | self.descriptionField.frame = CGRectMake((frame.size.width - pictureWidthWithFrame) / 2, pictureHeightWithFrame + contentGap, pictureWidthWithFrame, frame.size.height - (pictureHeightWithFrame + contentGap)); 147 | } 148 | else 149 | { 150 | if (maxPictureHeight > 360) 151 | { 152 | maxPictureHeight = 360; 153 | fitToHeightWidth = 480; 154 | } 155 | 156 | CGFloat pictureWidthWithFrame = fitToHeightWidth + 2 * FRAME_MARGIN; 157 | CGFloat pictureHeightWithFrame = maxPictureHeight + 2 * FRAME_MARGIN; 158 | 159 | self.imageFrame.frame = CGRectMake(0, (frame.size.height - pictureHeightWithFrame) / 2, pictureWidthWithFrame, pictureHeightWithFrame); 160 | self.descriptionField.frame = CGRectMake(pictureWidthWithFrame + contentGap, (frame.size.height - pictureHeightWithFrame) / 2, frame.size.width - (pictureWidthWithFrame + contentGap), pictureHeightWithFrame); 161 | } 162 | 163 | // during rotation we'll get a separate callback and animate the change in shadowPath 164 | if (![self isRotating]) 165 | [self setShadowPathsWithAnimationDuration:0]; 166 | 167 | NSLog(@"viewWillLayoutSubviews"); 168 | } 169 | 170 | - (void)setShadowPathsWithAnimationDuration:(NSTimeInterval)duration 171 | { 172 | UIBezierPath *newPath = [UIBezierPath bezierPathWithRect:self.imageFrame.bounds]; 173 | CGPathRef oldPath = CGPathRetain([self.imageFrame.layer shadowPath]); 174 | [self.imageFrame.layer setShadowPath:[newPath CGPath]]; 175 | 176 | if (duration > 0) 177 | { 178 | CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"]; 179 | [pathAnimation setFromValue:(__bridge id)oldPath]; 180 | [pathAnimation setToValue:(id)[self.imageFrame.layer shadowPath]]; 181 | [pathAnimation setDuration:duration]; 182 | [pathAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 183 | [pathAnimation setRemovedOnCompletion:YES]; 184 | 185 | [self.imageFrame.layer addAnimation:pathAnimation forKey:@"shadowPath"]; 186 | } 187 | 188 | CGPathRelease(oldPath); 189 | } 190 | 191 | - (void)viewDidLayoutSubviews 192 | { 193 | [super viewDidLayoutSubviews]; 194 | NSLog(@"viewDidLayoutSubviews"); 195 | } 196 | 197 | - (void)willMoveToParentViewController:(UIViewController *)parent 198 | { 199 | [super willMoveToParentViewController:parent]; 200 | if (parent) 201 | NSLog(@"willMoveToParentViewController"); 202 | else 203 | NSLog(@"willRemoveFromParentViewController"); 204 | } 205 | 206 | - (void)didMoveToParentViewController:(UIViewController *)parent 207 | { 208 | [super didMoveToParentViewController:parent]; 209 | if (parent) 210 | NSLog(@"didMoveToParentViewController"); 211 | else 212 | NSLog(@"didRemoveFromParentViewController"); 213 | } 214 | 215 | @end 216 | -------------------------------------------------------------------------------- /Demo/MPFlipViewController-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Flip Controller 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIcons 12 | 13 | CFBundlePrimaryIcon 14 | 15 | CFBundleIconFiles 16 | 17 | Icon.png 18 | Icon@2x.png 19 | Icon-72.png 20 | Icon-72@2x.png 21 | 22 | UIPrerenderedIcon 23 | 24 | 25 | 26 | CFBundleIdentifier 27 | com.markpospesel.${PRODUCT_NAME:rfc1034identifier} 28 | CFBundleInfoDictionaryVersion 29 | 6.0 30 | CFBundleName 31 | ${PRODUCT_NAME} 32 | CFBundlePackageType 33 | APPL 34 | CFBundleShortVersionString 35 | 1.0 36 | CFBundleSignature 37 | ???? 38 | CFBundleVersion 39 | 1.0 40 | LSRequiresIPhoneOS 41 | 42 | UIMainStoryboardFile 43 | MainStoryboard_iPhone 44 | UIMainStoryboardFile~ipad 45 | MainStoryboard_iPad 46 | UIPrerenderedIcon 47 | 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Demo/MPFlipViewController-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'MPFlipViewController' target in the 'MPFlipViewController' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Demo/Pattern - Aged Paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/Pattern - Aged Paper.png -------------------------------------------------------------------------------- /Demo/Pattern - Aged Paper@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/Pattern - Aged Paper@2x.png -------------------------------------------------------------------------------- /Demo/Pattern - Apple Wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/Pattern - Apple Wood.png -------------------------------------------------------------------------------- /Demo/Pattern - Apple Wood@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/Pattern - Apple Wood@2x.png -------------------------------------------------------------------------------- /Demo/Pattern - Corkboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/Pattern - Corkboard.png -------------------------------------------------------------------------------- /Demo/Pattern - Corkboard@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/Pattern - Corkboard@2x.png -------------------------------------------------------------------------------- /Demo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/4/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MPFlipViewController.h" 11 | 12 | @interface ViewController : UIViewController 13 | 14 | @property (strong, nonatomic) MPFlipViewController *flipViewController; 15 | @property (weak, nonatomic) IBOutlet UIView *frame; 16 | @property (weak, nonatomic) IBOutlet UIView *corkboard; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Demo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/4/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ContentViewController.h" 11 | #import 12 | 13 | #define CONTENT_IDENTIFIER @"ContentViewController" 14 | #define FRAME_MARGIN 60 15 | #define MOVIE_MIN 1 16 | #define MOVIE_MAX 3 17 | 18 | @interface ViewController () 19 | 20 | @property (assign, nonatomic) int previousIndex; 21 | @property (assign, nonatomic) int tentativeIndex; 22 | @property (assign, nonatomic) BOOL observerAdded; 23 | 24 | @end 25 | 26 | @implementation ViewController 27 | 28 | @synthesize frame = _frame; 29 | @synthesize flipViewController = _flipViewController; 30 | @synthesize corkboard = _corkboard; 31 | @synthesize previousIndex = _previousIndex; 32 | @synthesize tentativeIndex = _tentativeIndex; 33 | 34 | - (void)viewDidLoad 35 | { 36 | [super viewDidLoad]; 37 | // Do any additional setup after loading the view, typically from a nib. 38 | 39 | self.previousIndex = MOVIE_MIN; 40 | 41 | // Configure the page view controller and add it as a child view controller. 42 | self.flipViewController = [[MPFlipViewController alloc] initWithOrientation:[self flipViewController:nil orientationForInterfaceOrientation:[UIApplication sharedApplication].statusBarOrientation]]; 43 | self.flipViewController.delegate = self; 44 | self.flipViewController.dataSource = self; 45 | 46 | // Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages. 47 | BOOL hasFrame = self.frame != nil; 48 | CGRect pageViewRect = self.view.bounds; 49 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) 50 | { 51 | pageViewRect = CGRectInset(pageViewRect, 20 + (hasFrame? FRAME_MARGIN : 0), 20 + (hasFrame? FRAME_MARGIN : 0)); 52 | self.flipViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 53 | } 54 | else 55 | { 56 | pageViewRect = CGRectMake((self.view.bounds.size.width - 600)/2, (self.view.bounds.size.height - 600)/2, 600, 600); 57 | self.flipViewController.view.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 58 | } 59 | self.flipViewController.view.frame = pageViewRect; 60 | [self addChildViewController:self.flipViewController]; 61 | [self.view addSubview:self.flipViewController.view]; 62 | [self.flipViewController didMoveToParentViewController:self]; 63 | 64 | [self.flipViewController setViewController:[self contentViewWithIndex:self.previousIndex] direction:MPFlipViewControllerDirectionForward animated:NO completion:nil]; 65 | 66 | // Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily. 67 | self.view.gestureRecognizers = self.flipViewController.gestureRecognizers; 68 | 69 | [self.corkboard setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"Pattern - Corkboard"]]]; 70 | if (self.frame) 71 | { 72 | [self.frame setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"Pattern - Apple Wood"]]]; 73 | } 74 | 75 | [self addObserver]; 76 | } 77 | 78 | - (void)viewDidUnload 79 | { 80 | [self setCorkboard:nil]; 81 | [self setFrame:nil]; 82 | [self removeObserver]; 83 | [super viewDidUnload]; 84 | 85 | // Release any retained subviews of the main view. 86 | } 87 | 88 | - (void)addObserver 89 | { 90 | if (![self observerAdded]) 91 | { 92 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(flipViewControllerDidFinishAnimatingNotification:) name:MPFlipViewControllerDidFinishAnimatingNotification object:nil]; 93 | [self setObserverAdded:YES]; 94 | } 95 | } 96 | 97 | - (void)removeObserver 98 | { 99 | if ([self observerAdded]) 100 | { 101 | [[NSNotificationCenter defaultCenter] removeObserver:self name:MPFlipViewControllerDidFinishAnimatingNotification object:nil]; 102 | [self setObserverAdded:NO]; 103 | } 104 | } 105 | 106 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 107 | { 108 | if ([self flipViewController]) 109 | return [[self flipViewController] shouldAutorotateToInterfaceOrientation:interfaceOrientation]; 110 | else 111 | return YES; 112 | } 113 | 114 | - (NSString *)storyboardName 115 | { 116 | // fetch the appropriate storyboard name depending on platform 117 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) 118 | return @"MainStoryboard_iPhone"; 119 | else 120 | return @"MainStoryboard_iPad"; 121 | } 122 | 123 | - (ContentViewController *)contentViewWithIndex:(int)index 124 | { 125 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:[self storyboardName] bundle:nil]; 126 | ContentViewController *page = [storyboard instantiateViewControllerWithIdentifier:CONTENT_IDENTIFIER]; 127 | page.movieIndex = index; 128 | page.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 129 | return page; 130 | } 131 | 132 | #pragma mark - MPFlipViewControllerDelegate protocol 133 | 134 | - (void)flipViewController:(MPFlipViewController *)flipViewController didFinishAnimating:(BOOL)finished previousViewController:(UIViewController *)previousViewController transitionCompleted:(BOOL)completed 135 | { 136 | if (completed) 137 | { 138 | self.previousIndex = self.tentativeIndex; 139 | } 140 | } 141 | 142 | - (MPFlipViewControllerOrientation)flipViewController:(MPFlipViewController *)flipViewController orientationForInterfaceOrientation:(UIInterfaceOrientation)orientation 143 | { 144 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) 145 | return UIInterfaceOrientationIsPortrait(orientation)? MPFlipViewControllerOrientationVertical : MPFlipViewControllerOrientationHorizontal; 146 | else 147 | return MPFlipViewControllerOrientationHorizontal; 148 | } 149 | 150 | #pragma mark - MPFlipViewControllerDataSource protocol 151 | 152 | - (UIViewController *)flipViewController:(MPFlipViewController *)flipViewController viewControllerBeforeViewController:(UIViewController *)viewController 153 | { 154 | int index = self.previousIndex; 155 | index--; 156 | if (index < MOVIE_MIN) 157 | return nil; // reached beginning, don't wrap 158 | self.tentativeIndex = index; 159 | return [self contentViewWithIndex:index]; 160 | } 161 | 162 | - (UIViewController *)flipViewController:(MPFlipViewController *)flipViewController viewControllerAfterViewController:(UIViewController *)viewController 163 | { 164 | int index = self.previousIndex; 165 | index++; 166 | if (index > MOVIE_MAX) 167 | return nil; // reached end, don't wrap 168 | self.tentativeIndex = index; 169 | return [self contentViewWithIndex:index]; 170 | } 171 | 172 | #pragma mark - Notifications 173 | 174 | - (void)flipViewControllerDidFinishAnimatingNotification:(NSNotification *)notification 175 | { 176 | NSLog(@"Notification received: %@", notification); 177 | } 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /Demo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Demo/en.lproj/MainStoryboard_iPad.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /Demo/en.lproj/MainStoryboard_iPhone.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MPFlipViewController 4 | // 5 | // Created by Mark Pospesel on 6/4/12. 6 | // Copyright (c) 2012 Mark Pospesel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/matrix_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/matrix_01.png -------------------------------------------------------------------------------- /Demo/matrix_01@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/matrix_01@2x.png -------------------------------------------------------------------------------- /Demo/matrix_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/matrix_02.png -------------------------------------------------------------------------------- /Demo/matrix_02@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/matrix_02@2x.png -------------------------------------------------------------------------------- /Demo/matrix_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/matrix_03.png -------------------------------------------------------------------------------- /Demo/matrix_03@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Demo/matrix_03@2x.png -------------------------------------------------------------------------------- /Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Icon-72.png -------------------------------------------------------------------------------- /Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Icon-72@2x.png -------------------------------------------------------------------------------- /Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Icon.png -------------------------------------------------------------------------------- /Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpospese/MPFlipViewController/c8928d834c03c0529d91e2ce65e79b27633738ef/Icon@2x.png -------------------------------------------------------------------------------- /MPFlipViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4D09C382157CC025000CFE0F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D09C381157CC025000CFE0F /* UIKit.framework */; }; 11 | 4D09C384157CC025000CFE0F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D09C383157CC025000CFE0F /* Foundation.framework */; }; 12 | 4D09C386157CC025000CFE0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D09C385157CC025000CFE0F /* CoreGraphics.framework */; }; 13 | 4D09C38C157CC025000CFE0F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C38A157CC025000CFE0F /* InfoPlist.strings */; }; 14 | 4D09C38E157CC025000CFE0F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C38D157CC025000CFE0F /* main.m */; }; 15 | 4D09C392157CC025000CFE0F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C391157CC025000CFE0F /* AppDelegate.m */; }; 16 | 4D09C395157CC025000CFE0F /* MainStoryboard_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C393157CC025000CFE0F /* MainStoryboard_iPhone.storyboard */; }; 17 | 4D09C398157CC025000CFE0F /* MainStoryboard_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C396157CC025000CFE0F /* MainStoryboard_iPad.storyboard */; }; 18 | 4D09C39B157CC025000CFE0F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C39A157CC025000CFE0F /* ViewController.m */; }; 19 | 4D09C3A8157CC288000CFE0F /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D09C3A7157CC288000CFE0F /* QuartzCore.framework */; }; 20 | 4D09C3B4157CC63E000CFE0F /* matrix_01.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C3AC157CC63E000CFE0F /* matrix_01.png */; }; 21 | 4D09C3B5157CC63E000CFE0F /* matrix_01@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C3AD157CC63E000CFE0F /* matrix_01@2x.png */; }; 22 | 4D09C3B8157CC63E000CFE0F /* matrix_02.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C3B0157CC63E000CFE0F /* matrix_02.png */; }; 23 | 4D09C3B9157CC63E000CFE0F /* matrix_02@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C3B1157CC63E000CFE0F /* matrix_02@2x.png */; }; 24 | 4D09C3BA157CC63E000CFE0F /* matrix_03.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C3B2157CC63E000CFE0F /* matrix_03.png */; }; 25 | 4D09C3BB157CC63E000CFE0F /* matrix_03@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D09C3B3157CC63E000CFE0F /* matrix_03@2x.png */; }; 26 | 4D09C3BE157CC6BA000CFE0F /* MPAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C3BD157CC69E000CFE0F /* MPAnimation.m */; }; 27 | 4D09C3C1157CC71B000CFE0F /* MPFlipViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C3C0157CC71B000CFE0F /* MPFlipViewController.m */; }; 28 | 4D09C3D4157E16DB000CFE0F /* MPFlipTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C3CE157E16DB000CFE0F /* MPFlipTransition.m */; }; 29 | 4D09C3D5157E16DB000CFE0F /* MPTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C3CF157E16DB000CFE0F /* MPTransition.m */; }; 30 | 4D09C3D9157E38ED000CFE0F /* ContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D09C3D8157E38ED000CFE0F /* ContentViewController.m */; }; 31 | 4D16417215989D76004FD2DC /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D16417115989D76004FD2DC /* Icon.png */; }; 32 | 4D16417515989D7B004FD2DC /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D16417415989D7B004FD2DC /* Icon@2x.png */; }; 33 | 4D16417715989D84004FD2DC /* Icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D16417615989D84004FD2DC /* Icon-72.png */; }; 34 | 4D16417915989D88004FD2DC /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D16417815989D88004FD2DC /* Icon-72@2x.png */; }; 35 | 4D3916B6160D735300514EFD /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D3916B5160D735300514EFD /* Default-568h@2x.png */; }; 36 | 4D4D4E1815AAE2F4003DA356 /* Pattern - Aged Paper@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D4D4E1515AAE2F4003DA356 /* Pattern - Aged Paper@2x.png */; }; 37 | 4D4D4E1915AAE2F4003DA356 /* Pattern - Apple Wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D4D4E1615AAE2F4003DA356 /* Pattern - Apple Wood@2x.png */; }; 38 | 4D4D4E1A15AAE2F4003DA356 /* Pattern - Corkboard@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D4D4E1715AAE2F4003DA356 /* Pattern - Corkboard@2x.png */; }; 39 | 4D8A25CD1595CFC000789655 /* Pattern - Aged Paper.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D8A25CA1595CFC000789655 /* Pattern - Aged Paper.png */; }; 40 | 4D8A25CE1595CFC000789655 /* Pattern - Apple Wood.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D8A25CB1595CFC000789655 /* Pattern - Apple Wood.png */; }; 41 | 4D8A25CF1595CFC000789655 /* Pattern - Corkboard.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D8A25CC1595CFC000789655 /* Pattern - Corkboard.png */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 4D09C37D157CC025000CFE0F /* MPFlipViewController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MPFlipViewController.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 4D09C381157CC025000CFE0F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 47 | 4D09C383157CC025000CFE0F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 48 | 4D09C385157CC025000CFE0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 49 | 4D09C389157CC025000CFE0F /* MPFlipViewController-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MPFlipViewController-Info.plist"; sourceTree = ""; }; 50 | 4D09C38B157CC025000CFE0F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 51 | 4D09C38D157CC025000CFE0F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 52 | 4D09C38F157CC025000CFE0F /* MPFlipViewController-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPFlipViewController-Prefix.pch"; sourceTree = ""; }; 53 | 4D09C390157CC025000CFE0F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 54 | 4D09C391157CC025000CFE0F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 55 | 4D09C394157CC025000CFE0F /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPhone.storyboard; sourceTree = ""; }; 56 | 4D09C397157CC025000CFE0F /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPad.storyboard; sourceTree = ""; }; 57 | 4D09C399157CC025000CFE0F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 58 | 4D09C39A157CC025000CFE0F /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 59 | 4D09C3A7157CC288000CFE0F /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 60 | 4D09C3A9157CC496000CFE0F /* Source Code License.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = "Source Code License.rtf"; sourceTree = ""; }; 61 | 4D09C3AA157CC496000CFE0F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 62 | 4D09C3AC157CC63E000CFE0F /* matrix_01.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = matrix_01.png; sourceTree = ""; }; 63 | 4D09C3AD157CC63E000CFE0F /* matrix_01@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "matrix_01@2x.png"; sourceTree = ""; }; 64 | 4D09C3B0157CC63E000CFE0F /* matrix_02.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = matrix_02.png; sourceTree = ""; }; 65 | 4D09C3B1157CC63E000CFE0F /* matrix_02@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "matrix_02@2x.png"; sourceTree = ""; }; 66 | 4D09C3B2157CC63E000CFE0F /* matrix_03.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = matrix_03.png; sourceTree = ""; }; 67 | 4D09C3B3157CC63E000CFE0F /* matrix_03@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "matrix_03@2x.png"; sourceTree = ""; }; 68 | 4D09C3BC157CC69E000CFE0F /* MPAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAnimation.h; sourceTree = ""; }; 69 | 4D09C3BD157CC69E000CFE0F /* MPAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAnimation.m; sourceTree = ""; }; 70 | 4D09C3BF157CC71B000CFE0F /* MPFlipViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFlipViewController.h; sourceTree = ""; }; 71 | 4D09C3C0157CC71B000CFE0F /* MPFlipViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFlipViewController.m; sourceTree = ""; }; 72 | 4D09C3CE157E16DB000CFE0F /* MPFlipTransition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFlipTransition.m; sourceTree = ""; }; 73 | 4D09C3CF157E16DB000CFE0F /* MPTransition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTransition.m; sourceTree = ""; }; 74 | 4D09C3D0157E16DB000CFE0F /* MPFlipEnumerations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFlipEnumerations.h; sourceTree = ""; }; 75 | 4D09C3D1157E16DB000CFE0F /* MPFlipTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFlipTransition.h; sourceTree = ""; }; 76 | 4D09C3D2157E16DB000CFE0F /* MPTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTransition.h; sourceTree = ""; }; 77 | 4D09C3D3157E16DB000CFE0F /* MPTransitionEnumerations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTransitionEnumerations.h; sourceTree = ""; }; 78 | 4D09C3D7157E38ED000CFE0F /* ContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContentViewController.h; sourceTree = ""; }; 79 | 4D09C3D8157E38ED000CFE0F /* ContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContentViewController.m; sourceTree = ""; }; 80 | 4D16417115989D76004FD2DC /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; 81 | 4D16417415989D7B004FD2DC /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = ""; }; 82 | 4D16417615989D84004FD2DC /* Icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72.png"; sourceTree = ""; }; 83 | 4D16417815989D88004FD2DC /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = ""; }; 84 | 4D3916B5160D735300514EFD /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 85 | 4D4D4E1515AAE2F4003DA356 /* Pattern - Aged Paper@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Pattern - Aged Paper@2x.png"; sourceTree = ""; }; 86 | 4D4D4E1615AAE2F4003DA356 /* Pattern - Apple Wood@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Pattern - Apple Wood@2x.png"; sourceTree = ""; }; 87 | 4D4D4E1715AAE2F4003DA356 /* Pattern - Corkboard@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Pattern - Corkboard@2x.png"; sourceTree = ""; }; 88 | 4D8A25CA1595CFC000789655 /* Pattern - Aged Paper.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Pattern - Aged Paper.png"; sourceTree = ""; }; 89 | 4D8A25CB1595CFC000789655 /* Pattern - Apple Wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Pattern - Apple Wood.png"; sourceTree = ""; }; 90 | 4D8A25CC1595CFC000789655 /* Pattern - Corkboard.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Pattern - Corkboard.png"; sourceTree = ""; }; 91 | /* End PBXFileReference section */ 92 | 93 | /* Begin PBXFrameworksBuildPhase section */ 94 | 4D09C37A157CC025000CFE0F /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | 4D09C3A8157CC288000CFE0F /* QuartzCore.framework in Frameworks */, 99 | 4D09C382157CC025000CFE0F /* UIKit.framework in Frameworks */, 100 | 4D09C384157CC025000CFE0F /* Foundation.framework in Frameworks */, 101 | 4D09C386157CC025000CFE0F /* CoreGraphics.framework in Frameworks */, 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | /* End PBXFrameworksBuildPhase section */ 106 | 107 | /* Begin PBXGroup section */ 108 | 4D09C372157CC025000CFE0F = { 109 | isa = PBXGroup; 110 | children = ( 111 | 4D3916B5160D735300514EFD /* Default-568h@2x.png */, 112 | 4D16417815989D88004FD2DC /* Icon-72@2x.png */, 113 | 4D16417615989D84004FD2DC /* Icon-72.png */, 114 | 4D16417415989D7B004FD2DC /* Icon@2x.png */, 115 | 4D16417115989D76004FD2DC /* Icon.png */, 116 | 4D09C3AA157CC496000CFE0F /* README.md */, 117 | 4D09C3A9157CC496000CFE0F /* Source Code License.rtf */, 118 | 4D09C387157CC025000CFE0F /* Demo */, 119 | 4D09C3A6157CC197000CFE0F /* Container */, 120 | 4D09C380157CC025000CFE0F /* Frameworks */, 121 | 4D09C37E157CC025000CFE0F /* Products */, 122 | ); 123 | sourceTree = ""; 124 | }; 125 | 4D09C37E157CC025000CFE0F /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 4D09C37D157CC025000CFE0F /* MPFlipViewController.app */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | 4D09C380157CC025000CFE0F /* Frameworks */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 4D09C3A7157CC288000CFE0F /* QuartzCore.framework */, 137 | 4D09C381157CC025000CFE0F /* UIKit.framework */, 138 | 4D09C383157CC025000CFE0F /* Foundation.framework */, 139 | 4D09C385157CC025000CFE0F /* CoreGraphics.framework */, 140 | ); 141 | name = Frameworks; 142 | sourceTree = ""; 143 | }; 144 | 4D09C387157CC025000CFE0F /* Demo */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 4D09C3AB157CC5BE000CFE0F /* Images */, 148 | 4D09C390157CC025000CFE0F /* AppDelegate.h */, 149 | 4D09C391157CC025000CFE0F /* AppDelegate.m */, 150 | 4D09C393157CC025000CFE0F /* MainStoryboard_iPhone.storyboard */, 151 | 4D09C396157CC025000CFE0F /* MainStoryboard_iPad.storyboard */, 152 | 4D09C399157CC025000CFE0F /* ViewController.h */, 153 | 4D09C39A157CC025000CFE0F /* ViewController.m */, 154 | 4D09C388157CC025000CFE0F /* Supporting Files */, 155 | 4D09C3D7157E38ED000CFE0F /* ContentViewController.h */, 156 | 4D09C3D8157E38ED000CFE0F /* ContentViewController.m */, 157 | ); 158 | path = Demo; 159 | sourceTree = ""; 160 | }; 161 | 4D09C388157CC025000CFE0F /* Supporting Files */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 4D09C389157CC025000CFE0F /* MPFlipViewController-Info.plist */, 165 | 4D09C38A157CC025000CFE0F /* InfoPlist.strings */, 166 | 4D09C38D157CC025000CFE0F /* main.m */, 167 | 4D09C38F157CC025000CFE0F /* MPFlipViewController-Prefix.pch */, 168 | ); 169 | name = "Supporting Files"; 170 | sourceTree = ""; 171 | }; 172 | 4D09C3A6157CC197000CFE0F /* Container */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | 4D09C3BC157CC69E000CFE0F /* MPAnimation.h */, 176 | 4D09C3BD157CC69E000CFE0F /* MPAnimation.m */, 177 | 4D09C3BF157CC71B000CFE0F /* MPFlipViewController.h */, 178 | 4D09C3C0157CC71B000CFE0F /* MPFlipViewController.m */, 179 | 4D09C3D0157E16DB000CFE0F /* MPFlipEnumerations.h */, 180 | 4D09C3D3157E16DB000CFE0F /* MPTransitionEnumerations.h */, 181 | 4D09C3D1157E16DB000CFE0F /* MPFlipTransition.h */, 182 | 4D09C3CE157E16DB000CFE0F /* MPFlipTransition.m */, 183 | 4D09C3D2157E16DB000CFE0F /* MPTransition.h */, 184 | 4D09C3CF157E16DB000CFE0F /* MPTransition.m */, 185 | ); 186 | path = Container; 187 | sourceTree = ""; 188 | }; 189 | 4D09C3AB157CC5BE000CFE0F /* Images */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 4D09C3AC157CC63E000CFE0F /* matrix_01.png */, 193 | 4D09C3AD157CC63E000CFE0F /* matrix_01@2x.png */, 194 | 4D09C3B0157CC63E000CFE0F /* matrix_02.png */, 195 | 4D09C3B1157CC63E000CFE0F /* matrix_02@2x.png */, 196 | 4D09C3B2157CC63E000CFE0F /* matrix_03.png */, 197 | 4D09C3B3157CC63E000CFE0F /* matrix_03@2x.png */, 198 | 4D8A25CA1595CFC000789655 /* Pattern - Aged Paper.png */, 199 | 4D4D4E1515AAE2F4003DA356 /* Pattern - Aged Paper@2x.png */, 200 | 4D8A25CB1595CFC000789655 /* Pattern - Apple Wood.png */, 201 | 4D4D4E1615AAE2F4003DA356 /* Pattern - Apple Wood@2x.png */, 202 | 4D8A25CC1595CFC000789655 /* Pattern - Corkboard.png */, 203 | 4D4D4E1715AAE2F4003DA356 /* Pattern - Corkboard@2x.png */, 204 | ); 205 | name = Images; 206 | sourceTree = ""; 207 | }; 208 | /* End PBXGroup section */ 209 | 210 | /* Begin PBXNativeTarget section */ 211 | 4D09C37C157CC025000CFE0F /* MPFlipViewController */ = { 212 | isa = PBXNativeTarget; 213 | buildConfigurationList = 4D09C39E157CC025000CFE0F /* Build configuration list for PBXNativeTarget "MPFlipViewController" */; 214 | buildPhases = ( 215 | 4D09C379157CC025000CFE0F /* Sources */, 216 | 4D09C37A157CC025000CFE0F /* Frameworks */, 217 | 4D09C37B157CC025000CFE0F /* Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | ); 223 | name = MPFlipViewController; 224 | productName = MPFlipViewController; 225 | productReference = 4D09C37D157CC025000CFE0F /* MPFlipViewController.app */; 226 | productType = "com.apple.product-type.application"; 227 | }; 228 | /* End PBXNativeTarget section */ 229 | 230 | /* Begin PBXProject section */ 231 | 4D09C374157CC025000CFE0F /* Project object */ = { 232 | isa = PBXProject; 233 | attributes = { 234 | LastUpgradeCheck = 0430; 235 | ORGANIZATIONNAME = "Mark Pospesel"; 236 | }; 237 | buildConfigurationList = 4D09C377157CC025000CFE0F /* Build configuration list for PBXProject "MPFlipViewController" */; 238 | compatibilityVersion = "Xcode 3.2"; 239 | developmentRegion = English; 240 | hasScannedForEncodings = 0; 241 | knownRegions = ( 242 | en, 243 | ); 244 | mainGroup = 4D09C372157CC025000CFE0F; 245 | productRefGroup = 4D09C37E157CC025000CFE0F /* Products */; 246 | projectDirPath = ""; 247 | projectRoot = ""; 248 | targets = ( 249 | 4D09C37C157CC025000CFE0F /* MPFlipViewController */, 250 | ); 251 | }; 252 | /* End PBXProject section */ 253 | 254 | /* Begin PBXResourcesBuildPhase section */ 255 | 4D09C37B157CC025000CFE0F /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | 4D09C38C157CC025000CFE0F /* InfoPlist.strings in Resources */, 260 | 4D09C395157CC025000CFE0F /* MainStoryboard_iPhone.storyboard in Resources */, 261 | 4D09C398157CC025000CFE0F /* MainStoryboard_iPad.storyboard in Resources */, 262 | 4D09C3B4157CC63E000CFE0F /* matrix_01.png in Resources */, 263 | 4D09C3B5157CC63E000CFE0F /* matrix_01@2x.png in Resources */, 264 | 4D09C3B8157CC63E000CFE0F /* matrix_02.png in Resources */, 265 | 4D09C3B9157CC63E000CFE0F /* matrix_02@2x.png in Resources */, 266 | 4D09C3BA157CC63E000CFE0F /* matrix_03.png in Resources */, 267 | 4D09C3BB157CC63E000CFE0F /* matrix_03@2x.png in Resources */, 268 | 4D8A25CD1595CFC000789655 /* Pattern - Aged Paper.png in Resources */, 269 | 4D8A25CE1595CFC000789655 /* Pattern - Apple Wood.png in Resources */, 270 | 4D8A25CF1595CFC000789655 /* Pattern - Corkboard.png in Resources */, 271 | 4D16417215989D76004FD2DC /* Icon.png in Resources */, 272 | 4D16417515989D7B004FD2DC /* Icon@2x.png in Resources */, 273 | 4D16417715989D84004FD2DC /* Icon-72.png in Resources */, 274 | 4D16417915989D88004FD2DC /* Icon-72@2x.png in Resources */, 275 | 4D4D4E1815AAE2F4003DA356 /* Pattern - Aged Paper@2x.png in Resources */, 276 | 4D4D4E1915AAE2F4003DA356 /* Pattern - Apple Wood@2x.png in Resources */, 277 | 4D4D4E1A15AAE2F4003DA356 /* Pattern - Corkboard@2x.png in Resources */, 278 | 4D3916B6160D735300514EFD /* Default-568h@2x.png in Resources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | /* End PBXResourcesBuildPhase section */ 283 | 284 | /* Begin PBXSourcesBuildPhase section */ 285 | 4D09C379157CC025000CFE0F /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 4D09C38E157CC025000CFE0F /* main.m in Sources */, 290 | 4D09C392157CC025000CFE0F /* AppDelegate.m in Sources */, 291 | 4D09C39B157CC025000CFE0F /* ViewController.m in Sources */, 292 | 4D09C3BE157CC6BA000CFE0F /* MPAnimation.m in Sources */, 293 | 4D09C3C1157CC71B000CFE0F /* MPFlipViewController.m in Sources */, 294 | 4D09C3D4157E16DB000CFE0F /* MPFlipTransition.m in Sources */, 295 | 4D09C3D5157E16DB000CFE0F /* MPTransition.m in Sources */, 296 | 4D09C3D9157E38ED000CFE0F /* ContentViewController.m in Sources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | /* End PBXSourcesBuildPhase section */ 301 | 302 | /* Begin PBXVariantGroup section */ 303 | 4D09C38A157CC025000CFE0F /* InfoPlist.strings */ = { 304 | isa = PBXVariantGroup; 305 | children = ( 306 | 4D09C38B157CC025000CFE0F /* en */, 307 | ); 308 | name = InfoPlist.strings; 309 | sourceTree = ""; 310 | }; 311 | 4D09C393157CC025000CFE0F /* MainStoryboard_iPhone.storyboard */ = { 312 | isa = PBXVariantGroup; 313 | children = ( 314 | 4D09C394157CC025000CFE0F /* en */, 315 | ); 316 | name = MainStoryboard_iPhone.storyboard; 317 | sourceTree = ""; 318 | }; 319 | 4D09C396157CC025000CFE0F /* MainStoryboard_iPad.storyboard */ = { 320 | isa = PBXVariantGroup; 321 | children = ( 322 | 4D09C397157CC025000CFE0F /* en */, 323 | ); 324 | name = MainStoryboard_iPad.storyboard; 325 | sourceTree = ""; 326 | }; 327 | /* End PBXVariantGroup section */ 328 | 329 | /* Begin XCBuildConfiguration section */ 330 | 4D09C39C157CC025000CFE0F /* Debug */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 337 | COPY_PHASE_STRIP = NO; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_DYNAMIC_NO_PIC = NO; 340 | GCC_OPTIMIZATION_LEVEL = 0; 341 | GCC_PREPROCESSOR_DEFINITIONS = ( 342 | "DEBUG=1", 343 | "$(inherited)", 344 | ); 345 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 346 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 348 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 349 | GCC_WARN_UNUSED_VARIABLE = YES; 350 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 351 | SDKROOT = iphoneos; 352 | TARGETED_DEVICE_FAMILY = "1,2"; 353 | }; 354 | name = Debug; 355 | }; 356 | 4D09C39D157CC025000CFE0F /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ALWAYS_SEARCH_USER_PATHS = NO; 360 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 361 | CLANG_ENABLE_OBJC_ARC = YES; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 363 | COPY_PHASE_STRIP = YES; 364 | GCC_C_LANGUAGE_STANDARD = gnu99; 365 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 366 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 367 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 370 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 371 | SDKROOT = iphoneos; 372 | TARGETED_DEVICE_FAMILY = "1,2"; 373 | VALIDATE_PRODUCT = YES; 374 | }; 375 | name = Release; 376 | }; 377 | 4D09C39F157CC025000CFE0F /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 381 | GCC_PREFIX_HEADER = "Demo/MPFlipViewController-Prefix.pch"; 382 | INFOPLIST_FILE = "Demo/MPFlipViewController-Info.plist"; 383 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 384 | PRODUCT_NAME = "$(TARGET_NAME)"; 385 | WRAPPER_EXTENSION = app; 386 | }; 387 | name = Debug; 388 | }; 389 | 4D09C3A0157CC025000CFE0F /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 393 | GCC_PREFIX_HEADER = "Demo/MPFlipViewController-Prefix.pch"; 394 | INFOPLIST_FILE = "Demo/MPFlipViewController-Info.plist"; 395 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | WRAPPER_EXTENSION = app; 398 | }; 399 | name = Release; 400 | }; 401 | /* End XCBuildConfiguration section */ 402 | 403 | /* Begin XCConfigurationList section */ 404 | 4D09C377157CC025000CFE0F /* Build configuration list for PBXProject "MPFlipViewController" */ = { 405 | isa = XCConfigurationList; 406 | buildConfigurations = ( 407 | 4D09C39C157CC025000CFE0F /* Debug */, 408 | 4D09C39D157CC025000CFE0F /* Release */, 409 | ); 410 | defaultConfigurationIsVisible = 0; 411 | defaultConfigurationName = Release; 412 | }; 413 | 4D09C39E157CC025000CFE0F /* Build configuration list for PBXNativeTarget "MPFlipViewController" */ = { 414 | isa = XCConfigurationList; 415 | buildConfigurations = ( 416 | 4D09C39F157CC025000CFE0F /* Debug */, 417 | 4D09C3A0157CC025000CFE0F /* Release */, 418 | ); 419 | defaultConfigurationIsVisible = 0; 420 | defaultConfigurationName = Release; 421 | }; 422 | /* End XCConfigurationList section */ 423 | }; 424 | rootObject = 4D09C374157CC025000CFE0F /* Project object */; 425 | } 426 | -------------------------------------------------------------------------------- /MPFlipViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MPFlipViewController 2 | ==================== 3 | 4 | A custom container view controller following the iOS 5 containment API that navigates between child view controllers via touch gestures and page-flip animations 5 | 6 | This is the app to accompany my talk ["Implementing Custom Container View Controllers"](http://cocoaconf.com/conference/sessionDetails/82?confId=4) I presented at [CocoaConf DC 2012](http://cocoaconf.com/dc-2012/home). 7 | 8 | ![iPhone screenshot](http://markpospesel.files.wordpress.com/2012/07/fliphorizontal2x.png) 9 | 10 | Requirements 11 | ------------ 12 | * Xcode 4.4 or higher 13 | * LLVM compiler 14 | * iOS 5 or higher 15 | * ARC 16 | 17 | API 18 | ---------- 19 | The API is simple and modeled after UIKit's UIPageViewController. There is a Data Source protocol (to provide previous and next controllers) and a Delegate protocol. 20 | 21 | More details can be found in this [blog post](http://markpospesel.com/2012/07/28/mpflipviewcontroller/). See MPFlipViewController.h for full details. 22 | 23 | How To Use 24 | --------- 25 | Build and run the included demo app, and see how it works. The controller files are in the "Controller" directory and the demo app is in the "Demo" directory. 26 | 27 | The background images in the demo app are not freely distributable. They're by Glyphish and you should [get your own copy](http://glyphish.com/backgrounds/). While you're at it, they make [a great set of icons](http://glyphish.com/) too. 28 | 29 | Licensing 30 | --------- 31 | Read Source Code License.rtf, but the gist is: 32 | 33 | * Anyone can use it for any type of project 34 | * All I ask for is attribution somewhere 35 | 36 | Support, bugs and feature requests 37 | ---------------------------------- 38 | There is absolutely no support offered with this component. You're on your own! If you want to submit a feature request, please do so via [the issue tracker on github](https://github.com/mpospese/MPFlipViewController/issues). 39 | 40 | If you want to submit a bug report, please also do so via the issue tracker, including a diagnosis of the problem and a suggested fix (in code). If you're using MPFlipViewController, you're a developer - so I expect you to do your homework and provide a fix along with each bug report. You can also submit pull requests or patches. 41 | 42 | Please don't submit bug reports without fixes! 43 | 44 | (The preceding blurb provided courtesy of the legendary [Matt Gemmell](https://github.com/mattgemmell/)) 45 | 46 | Best, 47 | Mark Pospesel 48 | 49 | Website: http://markpospesel.com/ 50 | Contact: http://markpospesel.com/about 51 | Twitter: http://twitter.com/mpospese 52 | -------------------------------------------------------------------------------- /Source Code License.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 2 | {\fonttbl\f0\fnil\fcharset0 LucidaGrande;\f1\fmodern\fcharset0 Courier-Bold;\f2\fmodern\fcharset0 Courier; 3 | } 4 | {\colortbl;\red255\green255\blue255;\red247\green247\blue247;\red103\green103\blue103;\red77\green77\blue77; 5 | } 6 | \margl1440\margr1440\vieww21460\viewh13140\viewkind0 7 | \deftab720 8 | \pard\pardeftab720 9 | 10 | \f0\b\fs24 \cf0 \cb2 MPFlipViewController\ 11 | Mark Pospesel, Source Code License 12 | \b0 \cb2 \ 13 | 14 | \fs20 \cf3 \cb2 Last updated: April 19, 2012 15 | \fs24 \cf0 \cb2 \ 16 | \ 17 | The software license is based on the 3-clause "New BSD License", with an attribution requirement added. Basically you can use this code, original or modified, in your own products, including commercial and/or closed-source products. The only 2 things I ask are:\ 18 | \ 19 | 1. Include the full 20 | \f1\b\fs26 \cf4 \cb2 License Agreement 21 | \f0\b0\fs24 \cf0 \cb2 text below.\ 22 | 2. Include a credit mentioning me as the original author of the source (e.g. 23 | \b \cb2 Includes MPFlipViewController by Mark Pospesel 24 | \b0 \cb2 ). I would prefer that this credit be in the software's "About" window, but it could also be included in the list of Acknowledgments, in the software's documentation, or on the software's website.\ 25 | \ 26 | If you need an attribution-free version of this license for use in a product, please contact me via one of the methods listed under {\field{\*\fldinst{HYPERLINK "http://markpospesel.com/about"}}{\fldrslt http://markpospesel.com/about}} 27 | \fs26 \cb2 \ 28 | \ 29 | 30 | \f2 \ 31 | 32 | \f1\b \cf4 License Agreement 33 | \f2\b0 \ 34 | \ 35 | Copyright (c) 2012, Mark Pospesel\ 36 | All rights reserved.\ 37 | \ 38 | Redistribution and use in source and binary forms, with or without\ 39 | modification, are permitted provided that the following conditions are met:\ 40 | * Redistributions of source code must retain the above copyright\ 41 | notice, this list of conditions, attribution of Mark Pospesel as \ 42 | the original author of the source code and the following disclaimer.\ 43 | * Redistributions in binary form must reproduce the above copyright\ 44 | notice, this list of conditions, attribution of Mark Pospesel as \ 45 | the original author of the source code and the following disclaimer in the\ 46 | documentation and/or other materials provided with the distribution.\ 47 | * The name of Mark Pospesel may not be used to endorse or promote products\ 48 | derived from this software without specific prior written permission.\ 49 | \ 50 | THIS SOFTWARE IS PROVIDED BY MARK POSPESEL "AS IS" AND\ 51 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\ 52 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\ 53 | DISCLAIMED. IN NO EVENT SHALL MARK POSPESEL BE LIABLE FOR ANY\ 54 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\ 55 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\ 56 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\ 57 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\ 58 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\ 59 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.} --------------------------------------------------------------------------------