added iOS source code
[wl-app.git] / iOS / Pods / ZFDragableModalTransition / Classes / ZFModalTransitionAnimator.m
1 //
2 //  ZFModalTransitionAnimator.m
3 //
4 //  Created by Amornchai Kanokpullwad on 5/10/14.
5 //  Copyright (c) 2014 zoonref. All rights reserved.
6 //
7
8 #import "ZFModalTransitionAnimator.h"
9
10 @interface ZFModalTransitionAnimator ()
11 @property (nonatomic, weak) UIViewController *modalController;
12 @property (nonatomic, strong) ZFDetectScrollViewEndGestureRecognizer *gesture;
13 @property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;
14 @property CGFloat panLocationStart;
15 @property BOOL isDismiss;
16 @property BOOL isInteractive;
17 @property CATransform3D tempTransform;
18 @end
19
20 @implementation ZFModalTransitionAnimator
21
22 - (instancetype)initWithModalViewController:(UIViewController *)modalViewController
23 {
24     self = [super init];
25     if (self) {
26         _modalController = modalViewController;
27         _direction = ZFModalTransitonDirectionBottom;
28         _dragable = NO;
29         _bounces = YES;
30         _behindViewScale = 0.9f;
31         _behindViewAlpha = 1.0f;
32         _transitionDuration = 0.8f;
33
34         [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
35         [[NSNotificationCenter defaultCenter] addObserver:self
36                                                  selector:@selector(orientationChanged:)
37                                                      name:UIApplicationDidChangeStatusBarFrameNotification
38                                                    object:nil];
39     }
40     return self;
41 }
42
43 - (void)dealloc
44 {
45     [[NSNotificationCenter defaultCenter] removeObserver:self];
46     [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
47 }
48
49 - (void)setDragable:(BOOL)dragable
50 {
51     _dragable = dragable;
52     if (_dragable) {
53         [self removeGestureRecognizerFromModalController];
54         self.gesture = [[ZFDetectScrollViewEndGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
55         self.gesture.delegate = self;
56         [self.modalController.view addGestureRecognizer:self.gesture];
57     } else {
58         [self removeGestureRecognizerFromModalController];
59     }
60 }
61
62 - (void)setContentScrollView:(UIScrollView *)scrollView
63 {
64     // always enable drag if scrollview is set
65     if (!self.dragable) {
66         self.dragable = YES;
67     }
68     // and scrollview will work only for bottom mode
69     self.direction = ZFModalTransitonDirectionBottom;
70     self.gesture.scrollview = scrollView;
71 }
72
73 - (void)setDirection:(ZFModalTransitonDirection)direction
74 {
75     _direction = direction;
76     // scrollview will work only for bottom mode
77     if (_direction != ZFModalTransitonDirectionBottom) {
78         self.gesture.scrollview = nil;
79     }
80 }
81
82 - (void)animationEnded:(BOOL)transitionCompleted
83 {
84     // Reset to our default state
85     self.isInteractive = NO;
86     self.transitionContext = nil;
87 }
88
89 - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
90 {
91     return self.transitionDuration;
92 }
93
94 - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
95 {
96     if (self.isInteractive) {
97         return;
98     }
99     // Grab the from and to view controllers from the context
100     UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
101     UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
102
103     UIView *containerView = [transitionContext containerView];
104
105     if (!self.isDismiss) {
106
107         CGRect startRect;
108
109         [containerView addSubview:toViewController.view];
110
111         toViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
112
113         if (self.direction == ZFModalTransitonDirectionBottom) {
114             startRect = CGRectMake(0,
115                                    CGRectGetHeight(containerView.frame),
116                                    CGRectGetWidth(containerView.bounds),
117                                    CGRectGetHeight(containerView.bounds));
118         } else if (self.direction == ZFModalTransitonDirectionLeft) {
119             startRect = CGRectMake(-CGRectGetWidth(containerView.frame),
120                                    0,
121                                    CGRectGetWidth(containerView.bounds),
122                                    CGRectGetHeight(containerView.bounds));
123         } else if (self.direction == ZFModalTransitonDirectionRight) {
124             startRect = CGRectMake(CGRectGetWidth(containerView.frame),
125                                    0,
126                                    CGRectGetWidth(containerView.bounds),
127                                    CGRectGetHeight(containerView.bounds));
128         }
129
130         CGPoint transformedPoint = CGPointApplyAffineTransform(startRect.origin, toViewController.view.transform);
131         toViewController.view.frame = CGRectMake(transformedPoint.x, transformedPoint.y, startRect.size.width, startRect.size.height);
132
133         if (toViewController.modalPresentationStyle == UIModalPresentationCustom) {
134             [fromViewController beginAppearanceTransition:NO animated:YES];
135         }
136
137         [UIView animateWithDuration:[self transitionDuration:transitionContext]
138                               delay:0
139              usingSpringWithDamping:0.8
140               initialSpringVelocity:0.1
141                             options:UIViewAnimationOptionCurveEaseOut
142                          animations:^{
143                              fromViewController.view.transform = CGAffineTransformScale(fromViewController.view.transform, self.behindViewScale, self.behindViewScale);
144                              fromViewController.view.alpha = self.behindViewAlpha;
145
146                              toViewController.view.frame = CGRectMake(0,0,
147                                                                       CGRectGetWidth(toViewController.view.frame),
148                                                                       CGRectGetHeight(toViewController.view.frame));
149                          } completion:^(BOOL finished) {
150                              if (toViewController.modalPresentationStyle == UIModalPresentationCustom) {
151                                  [fromViewController endAppearanceTransition];
152                              }
153                              [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
154                          }];
155     } else {
156
157         if (fromViewController.modalPresentationStyle == UIModalPresentationFullScreen) {
158             [containerView addSubview:toViewController.view];
159         }
160         
161         [containerView bringSubviewToFront:fromViewController.view];
162
163         if (![self isPriorToIOS8]) {
164             toViewController.view.layer.transform = CATransform3DScale(toViewController.view.layer.transform, self.behindViewScale, self.behindViewScale, 1);
165         }
166
167         toViewController.view.alpha = self.behindViewAlpha;
168
169         CGRect endRect;
170
171         if (self.direction == ZFModalTransitonDirectionBottom) {
172             endRect = CGRectMake(0,
173                                  CGRectGetHeight(fromViewController.view.bounds),
174                                  CGRectGetWidth(fromViewController.view.frame),
175                                  CGRectGetHeight(fromViewController.view.frame));
176         } else if (self.direction == ZFModalTransitonDirectionLeft) {
177             endRect = CGRectMake(-CGRectGetWidth(fromViewController.view.bounds),
178                                  0,
179                                  CGRectGetWidth(fromViewController.view.frame),
180                                  CGRectGetHeight(fromViewController.view.frame));
181         } else if (self.direction == ZFModalTransitonDirectionRight) {
182             endRect = CGRectMake(CGRectGetWidth(fromViewController.view.bounds),
183                                  0,
184                                  CGRectGetWidth(fromViewController.view.frame),
185                                  CGRectGetHeight(fromViewController.view.frame));
186         }
187
188         CGPoint transformedPoint = CGPointApplyAffineTransform(endRect.origin, fromViewController.view.transform);
189         endRect = CGRectMake(transformedPoint.x, transformedPoint.y, endRect.size.width, endRect.size.height);
190
191         if (fromViewController.modalPresentationStyle == UIModalPresentationCustom) {
192             [toViewController beginAppearanceTransition:YES animated:YES];
193         }
194
195         [UIView animateWithDuration:[self transitionDuration:transitionContext]
196                               delay:0
197              usingSpringWithDamping:0.8
198               initialSpringVelocity:0.1
199                             options:UIViewAnimationOptionCurveEaseOut
200                          animations:^{
201                              CGFloat scaleBack = (1 / self.behindViewScale);
202                              toViewController.view.layer.transform = CATransform3DScale(toViewController.view.layer.transform, scaleBack, scaleBack, 1);
203                              toViewController.view.alpha = 1.0f;
204                              fromViewController.view.frame = endRect;
205                          } completion:^(BOOL finished) {
206                              toViewController.view.layer.transform = CATransform3DIdentity;
207                              if (fromViewController.modalPresentationStyle == UIModalPresentationCustom) {
208                                  [toViewController endAppearanceTransition];
209                              }
210                              [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
211                          }];
212     }
213 }
214
215 - (void)removeGestureRecognizerFromModalController
216 {
217     if (self.gesture && [self.modalController.view.gestureRecognizers containsObject:self.gesture]) {
218         [self.modalController.view removeGestureRecognizer:self.gesture];
219         self.gesture = nil;
220     }
221 }
222
223 # pragma mark - Gesture
224
225 - (void)handlePan:(UIPanGestureRecognizer *)recognizer
226 {
227     // Location reference
228     CGPoint location = [recognizer locationInView:self.modalController.view.window];
229     location = CGPointApplyAffineTransform(location, CGAffineTransformInvert(recognizer.view.transform));
230     // Velocity reference
231     CGPoint velocity = [recognizer velocityInView:[self.modalController.view window]];
232     velocity = CGPointApplyAffineTransform(velocity, CGAffineTransformInvert(recognizer.view.transform));
233
234     if (recognizer.state == UIGestureRecognizerStateBegan) {
235         self.isInteractive = YES;
236         if (self.direction == ZFModalTransitonDirectionBottom) {
237             self.panLocationStart = location.y;
238         } else {
239             self.panLocationStart = location.x;
240         }
241         [self.modalController dismissViewControllerAnimated:YES completion:nil];
242         
243     } else if (recognizer.state == UIGestureRecognizerStateChanged) {
244         CGFloat animationRatio = 0;
245
246         if (self.direction == ZFModalTransitonDirectionBottom) {
247             animationRatio = (location.y - self.panLocationStart) / (CGRectGetHeight([self.modalController view].bounds));
248         } else if (self.direction == ZFModalTransitonDirectionLeft) {
249             animationRatio = (self.panLocationStart - location.x) / (CGRectGetWidth([self.modalController view].bounds));
250         } else if (self.direction == ZFModalTransitonDirectionRight) {
251             animationRatio = (location.x - self.panLocationStart) / (CGRectGetWidth([self.modalController view].bounds));
252         }
253
254         [self updateInteractiveTransition:animationRatio];
255         
256     } else if (recognizer.state == UIGestureRecognizerStateEnded) {
257
258         CGFloat velocityForSelectedDirection;
259
260         if (self.direction == ZFModalTransitonDirectionBottom) {
261             velocityForSelectedDirection = velocity.y;
262         } else {
263             velocityForSelectedDirection = velocity.x;
264         }
265
266         if (velocityForSelectedDirection > 100
267             && (self.direction == ZFModalTransitonDirectionRight
268                 || self.direction == ZFModalTransitonDirectionBottom)) {
269                 [self finishInteractiveTransition];
270             } else if (velocityForSelectedDirection < -100 && self.direction == ZFModalTransitonDirectionLeft) {
271                 [self finishInteractiveTransition];
272             } else {
273                 [self cancelInteractiveTransition];
274             }
275         self.isInteractive = NO;
276     }
277 }
278
279 #pragma mark -
280
281 -(void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext
282 {
283     self.transitionContext = transitionContext;
284
285     UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
286     UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
287
288     if (![self isPriorToIOS8]) {
289         toViewController.view.layer.transform = CATransform3DScale(toViewController.view.layer.transform, self.behindViewScale, self.behindViewScale, 1);
290     }
291
292     self.tempTransform = toViewController.view.layer.transform;
293
294     toViewController.view.alpha = self.behindViewAlpha;
295     
296     if (fromViewController.modalPresentationStyle == UIModalPresentationFullScreen) {
297         [[transitionContext containerView] addSubview:toViewController.view];
298     }
299     [[transitionContext containerView] bringSubviewToFront:fromViewController.view];
300 }
301
302 - (void)updateInteractiveTransition:(CGFloat)percentComplete
303 {
304     if (!self.bounces && percentComplete < 0) {
305         percentComplete = 0;
306     }
307
308     id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext;
309
310     UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
311     UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
312     CATransform3D transform = CATransform3DMakeScale(
313                                                      1 + (((1 / self.behindViewScale) - 1) * percentComplete),
314                                                      1 + (((1 / self.behindViewScale) - 1) * percentComplete), 1);
315     toViewController.view.layer.transform = CATransform3DConcat(self.tempTransform, transform);
316
317     toViewController.view.alpha = self.behindViewAlpha + ((1 - self.behindViewAlpha) * percentComplete);
318
319     CGRect updateRect;
320     if (self.direction == ZFModalTransitonDirectionBottom) {
321         updateRect = CGRectMake(0,
322                                 (CGRectGetHeight(fromViewController.view.bounds) * percentComplete),
323                                 CGRectGetWidth(fromViewController.view.frame),
324                                 CGRectGetHeight(fromViewController.view.frame));
325     } else if (self.direction == ZFModalTransitonDirectionLeft) {
326         updateRect = CGRectMake(-(CGRectGetWidth(fromViewController.view.bounds) * percentComplete),
327                                 0,
328                                 CGRectGetWidth(fromViewController.view.frame),
329                                 CGRectGetHeight(fromViewController.view.frame));
330     } else if (self.direction == ZFModalTransitonDirectionRight) {
331         updateRect = CGRectMake(CGRectGetWidth(fromViewController.view.bounds) * percentComplete,
332                                 0,
333                                 CGRectGetWidth(fromViewController.view.frame),
334                                 CGRectGetHeight(fromViewController.view.frame));
335     }
336
337     // reset to zero if x and y has unexpected value to prevent crash
338     if (isnan(updateRect.origin.x) || isinf(updateRect.origin.x)) {
339         updateRect.origin.x = 0;
340     }
341     if (isnan(updateRect.origin.y) || isinf(updateRect.origin.y)) {
342         updateRect.origin.y = 0;
343     }
344
345     CGPoint transformedPoint = CGPointApplyAffineTransform(updateRect.origin, fromViewController.view.transform);
346     updateRect = CGRectMake(transformedPoint.x, transformedPoint.y, updateRect.size.width, updateRect.size.height);
347
348     fromViewController.view.frame = updateRect;
349 }
350
351 - (void)finishInteractiveTransition
352 {
353     id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext;
354
355     UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
356     UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
357
358     CGRect endRect;
359
360     if (self.direction == ZFModalTransitonDirectionBottom) {
361         endRect = CGRectMake(0,
362                              CGRectGetHeight(fromViewController.view.bounds),
363                              CGRectGetWidth(fromViewController.view.frame),
364                              CGRectGetHeight(fromViewController.view.frame));
365     } else if (self.direction == ZFModalTransitonDirectionLeft) {
366         endRect = CGRectMake(-CGRectGetWidth(fromViewController.view.bounds),
367                              0,
368                              CGRectGetWidth(fromViewController.view.frame),
369                              CGRectGetHeight(fromViewController.view.frame));
370     } else if (self.direction == ZFModalTransitonDirectionRight) {
371         endRect = CGRectMake(CGRectGetWidth(fromViewController.view.bounds),
372                              0,
373                              CGRectGetWidth(fromViewController.view.frame),
374                              CGRectGetHeight(fromViewController.view.frame));
375     }
376
377     CGPoint transformedPoint = CGPointApplyAffineTransform(endRect.origin, fromViewController.view.transform);
378     endRect = CGRectMake(transformedPoint.x, transformedPoint.y, endRect.size.width, endRect.size.height);
379
380     if (fromViewController.modalPresentationStyle == UIModalPresentationCustom) {
381         [toViewController beginAppearanceTransition:YES animated:YES];
382     }
383     
384     [UIView animateWithDuration:[self transitionDuration:transitionContext]
385                           delay:0
386          usingSpringWithDamping:0.8
387           initialSpringVelocity:0.1
388                         options:UIViewAnimationOptionCurveEaseOut
389                      animations:^{
390                          CGFloat scaleBack = (1 / self.behindViewScale);
391                          toViewController.view.layer.transform = CATransform3DScale(self.tempTransform, scaleBack, scaleBack, 1);
392                          toViewController.view.alpha = 1.0f;
393                          fromViewController.view.frame = endRect;
394                      } completion:^(BOOL finished) {
395                          if (fromViewController.modalPresentationStyle == UIModalPresentationCustom) {
396                              [toViewController endAppearanceTransition];
397                          }
398                          [transitionContext completeTransition:YES];
399                      }];
400 }
401
402 - (void)cancelInteractiveTransition
403 {
404     id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext;
405
406     UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
407     UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
408
409     [UIView animateWithDuration:0.4
410                           delay:0
411          usingSpringWithDamping:0.8
412           initialSpringVelocity:0.1
413                         options:UIViewAnimationOptionCurveEaseOut
414                      animations:^{
415                          toViewController.view.layer.transform = self.tempTransform;
416                          toViewController.view.alpha = self.behindViewAlpha;
417
418                          fromViewController.view.frame = CGRectMake(0,0,
419                                                                     CGRectGetWidth(fromViewController.view.frame),
420                                                                     CGRectGetHeight(fromViewController.view.frame));
421                      } completion:^(BOOL finished) {
422                          [transitionContext completeTransition:NO];
423                          if (fromViewController.modalPresentationStyle == UIModalPresentationFullScreen) {
424                              [toViewController.view removeFromSuperview];
425                          }
426                      }];
427 }
428
429 #pragma mark - UIViewControllerTransitioningDelegate Methods
430
431 - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
432 {
433     self.isDismiss = NO;
434     return self;
435 }
436
437 - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
438 {
439     self.isDismiss = YES;
440     return self;
441 }
442
443 - (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator
444 {
445     return nil;
446 }
447
448 - (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator
449 {
450     // Return nil if we are not interactive
451     if (self.isInteractive && self.dragable) {
452         self.isDismiss = YES;
453         return self;
454     }
455
456     return nil;
457 }
458
459 #pragma mark - Gesture Delegate
460
461 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
462 {
463     if (self.direction == ZFModalTransitonDirectionBottom) {
464         return YES;
465     }
466     return NO;
467 }
468
469 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
470 {
471     if (self.direction == ZFModalTransitonDirectionBottom) {
472         return YES;
473     }
474     return NO;
475 }
476
477 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
478     if (self.gestureRecognizerToFailPan && otherGestureRecognizer && self.gestureRecognizerToFailPan == otherGestureRecognizer) {
479         return YES;
480     }
481     
482     return NO;
483 }
484
485 #pragma mark - Utils
486
487 - (BOOL)isPriorToIOS8
488 {
489     NSComparisonResult order = [[UIDevice currentDevice].systemVersion compare: @"8.0" options: NSNumericSearch];
490     if (order == NSOrderedSame || order == NSOrderedDescending) {
491         // OS version >= 8.0
492         return YES;
493     }
494     return NO;
495 }
496
497 #pragma mark - Orientation
498
499 - (void)orientationChanged:(NSNotification *)notification
500 {
501     UIViewController *backViewController = self.modalController.presentingViewController;
502     backViewController.view.transform = CGAffineTransformIdentity;
503     backViewController.view.frame = self.modalController.view.bounds;
504     backViewController.view.transform = CGAffineTransformScale(backViewController.view.transform, self.behindViewScale, self.behindViewScale);
505 }
506
507 @end
508
509 // Gesture Class Implement
510 @interface ZFDetectScrollViewEndGestureRecognizer ()
511 @property (nonatomic, strong) NSNumber *isFail;
512 @end
513
514 @implementation ZFDetectScrollViewEndGestureRecognizer
515
516 - (void)reset
517 {
518     [super reset];
519     self.isFail = nil;
520 }
521
522 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
523 {
524     [super touchesMoved:touches withEvent:event];
525
526     if (!self.scrollview) {
527         return;
528     }
529
530     if (self.state == UIGestureRecognizerStateFailed) return;
531     CGPoint velocity = [self velocityInView:self.view];
532     CGPoint nowPoint = [touches.anyObject locationInView:self.view];
533     CGPoint prevPoint = [touches.anyObject previousLocationInView:self.view];
534
535     if (self.isFail) {
536         if (self.isFail.boolValue) {
537             self.state = UIGestureRecognizerStateFailed;
538         }
539         return;
540     }
541
542     CGFloat topVerticalOffset = -self.scrollview.contentInset.top;
543
544     if ((fabs(velocity.x) < fabs(velocity.y)) && (nowPoint.y > prevPoint.y) && (self.scrollview.contentOffset.y <= topVerticalOffset)) {
545         self.isFail = @NO;
546     } else if (self.scrollview.contentOffset.y >= topVerticalOffset) {
547         self.state = UIGestureRecognizerStateFailed;
548         self.isFail = @YES;
549     } else {
550         self.isFail = @NO;
551     }
552 }
553
554 @end