CustomTransitions/Slide/AAPLSlideTransitionDelegate.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
The delegate of the tab bar controller for the Slide demo. Manages the |
gesture recognizer used for the interactive transition. Vends |
instances of AAPLSlideTransitionAnimator and |
AAPLSlideTransitionInteractionController. |
*/ |
#import "AAPLSlideTransitionDelegate.h" |
#import "AAPLSlideTransitionAnimator.h" |
#import "AAPLSlideTransitionInteractionController.h" |
#import <objc/runtime.h> |
//! They key used to associate an instance of AAPLSlideTransitionDelegate with |
//! the tab bar controller for which it is the delegate. |
const char * AAPLSlideTabBarControllerDelegateAssociationKey = "AAPLSlideTabBarControllerDelegateAssociation"; |
@interface AAPLSlideTransitionDelegate () |
@property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer; |
@end |
@implementation AAPLSlideTransitionDelegate |
//| ---------------------------------------------------------------------------- |
// Custom implementation of the setter for the tabBarController property. |
// |
// An instance of the AAPLSlideTransitionDelegate class is defined in the |
// Tab Bar Controller's scene in the storyboard, and its tabBarControllerOutlet |
// connected to the tab bar controller. At unarchive time this method will |
// be called, providing an opportunity to perform the necessary setup. |
// |
- (void)setTabBarController:(UITabBarController *)tabBarController |
{ |
if (tabBarController != _tabBarController) { |
// Remove all associations of this object from the old tab bar |
// controller. |
objc_setAssociatedObject(_tabBarController, AAPLSlideTabBarControllerDelegateAssociationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
[_tabBarController.view removeGestureRecognizer:self.panGestureRecognizer]; |
if (_tabBarController.delegate == self) _tabBarController.delegate = nil; |
_tabBarController = tabBarController; |
_tabBarController.delegate = self; |
[_tabBarController.view addGestureRecognizer:self.panGestureRecognizer]; |
// Associate this object with the new tab bar controller. This ensures |
// that this object wil not be deallocated prior to the tab bar |
// controller being deallocated. |
objc_setAssociatedObject(_tabBarController, AAPLSlideTabBarControllerDelegateAssociationKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
} |
} |
#pragma mark - |
#pragma mark Gesture Recognizer |
//| ---------------------------------------------------------------------------- |
// Custom implementation of the getter for the panGestureRecognizer property. |
// Lazily creates the pan gesture recognizer for the tab bar controller. |
// |
- (UIPanGestureRecognizer*)panGestureRecognizer |
{ |
if (_panGestureRecognizer == nil) |
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognizerDidPan:)]; |
return _panGestureRecognizer; |
} |
//| ---------------------------------------------------------------------------- |
//! Action method for the panGestureRecognizer. |
// |
- (IBAction)panGestureRecognizerDidPan:(UIPanGestureRecognizer*)sender |
{ |
// Do not attempt to begin an interactive transition if one is already |
// ongoing |
if (self.tabBarController.transitionCoordinator) |
return; |
if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateChanged) |
[self beginInteractiveTransitionIfPossible:sender]; |
// Remaining cases are handled by the vended |
// AAPLSlideTransitionInteractionController. |
} |
//| ---------------------------------------------------------------------------- |
//! Begins an interactive transition with the provided gesture recognizer, if |
//! there is a view controller to transition to. |
// |
- (void)beginInteractiveTransitionIfPossible:(UIPanGestureRecognizer *)sender |
{ |
CGPoint translation = [sender translationInView:self.tabBarController.view]; |
if (translation.x > 0.f && self.tabBarController.selectedIndex > 0) { |
// Panning right, transition to the left view controller. |
self.tabBarController.selectedIndex--; |
} else if (translation.x < 0.f && self.tabBarController.selectedIndex + 1 < self.tabBarController.viewControllers.count) { |
// Panning left, transition to the right view controller. |
self.tabBarController.selectedIndex++; |
} else { |
// Don't reset the gesture recognizer if we skipped starting the |
// transition because we don't have a translation yet (and thus, could |
// not determine the transition direction). |
if (!CGPointEqualToPoint(translation, CGPointZero)) { |
// There is not a view controller to transition to, force the |
// gesture recognizer to fail. |
sender.enabled = NO; |
sender.enabled = YES; |
} |
} |
// We must handle the case in which the user begins panning but then |
// reverses direction without lifting their finger. The transition |
// should seamlessly switch to revealing the correct view controller |
// for the new direction. |
// |
// The approach presented in this demonstration relies on coordination |
// between this object and the AAPLSlideTransitionInteractionController |
// it vends. If the AAPLSlideTransitionInteractionController detects |
// that the current position of the user's touch along the horizontal |
// axis has crossed over the initial position, it cancels the |
// transition. A completion block is attached to the tab bar |
// controller's transition coordinator. This block will be called when |
// the transition completes or is cancelled. If the transition was |
// cancelled but the gesture recgonzier has not transitioned to the |
// ended or failed state, a new transition to the proper view controller |
// is started, and new animation + interaction controllers are created. |
// |
[self.tabBarController.transitionCoordinator animateAlongsideTransition:NULL completion:^(id<UIViewControllerTransitionCoordinatorContext> context) { |
if ([context isCancelled] && sender.state == UIGestureRecognizerStateChanged) |
[self beginInteractiveTransitionIfPossible:sender]; |
}]; |
} |
#pragma mark - |
#pragma mark UITabBarControllerDelegate |
//| ---------------------------------------------------------------------------- |
// The tab bar controller tries to invoke this method on its delegate to |
// retrieve an animator object to be used for animating the transition to the |
// incoming view controller. Your implementation is expected to return an |
// object that conforms to the UIViewControllerAnimatedTransitioning protocol, |
// or nil if the transition should not be animated. |
// |
- (id<UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC |
{ |
NSAssert(tabBarController == self.tabBarController, @"%@ is not the tab bar controller currently associated with %@", tabBarController, self); |
NSArray *viewControllers = tabBarController.viewControllers; |
if ([viewControllers indexOfObject:toVC] > [viewControllers indexOfObject:fromVC]) { |
// The incoming view controller succeeds the outgoing view controller, |
// slide towards the left. |
return [[AAPLSlideTransitionAnimator alloc] initWithTargetEdge:UIRectEdgeLeft]; |
} else { |
// The incoming view controller precedes the outgoing view controller, |
// slide towards the right. |
return [[AAPLSlideTransitionAnimator alloc] initWithTargetEdge:UIRectEdgeRight]; |
} |
} |
//| ---------------------------------------------------------------------------- |
// If an id<UIViewControllerAnimatedTransitioning> was returned from |
// -tabBarController:animationControllerForTransitionFromViewController:toViewController:, |
// the tab bar controller tries to invoke this method on its delegate to |
// retrieve an interaction controller for the transition. Your implementation |
// is expected to return an object that conforms to the |
// UIViewControllerInteractiveTransitioning protocol, or nil if the transition |
// should not be a interactive. |
// |
- (id<UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController |
{ |
NSAssert(tabBarController == self.tabBarController, @"%@ is not the tab bar controller currently associated with %@", tabBarController, self); |
if (self.panGestureRecognizer.state == UIGestureRecognizerStateBegan || self.panGestureRecognizer.state == UIGestureRecognizerStateChanged) { |
return [[AAPLSlideTransitionInteractionController alloc] initWithGestureRecognizer:self.panGestureRecognizer]; |
} else { |
// You must not return an interaction controller from this method unless |
// the transition will be interactive. |
return nil; |
} |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-01-28