Deprecated, please use RxCocoa
#SHTransitionBlocks
This pod is used by
SHUIKitBlocks
as part of many components covering to plug the holes missing from Foundation, UIKit, CoreLocation, GameKit, MapKit and other aspects of an iOS application's architecture.
###Overview
Transition API via blocks for AnimationTransition and InteractiveTransitions. InteractiveTransitions comes with block helpers for dealing with gestures and progress. All via categorizes on top of UIViewController, so it works well with UINavigationController and UITabBarController
pod 'SHTransitionBlocks'
Put this either in specific files or your project prefix file
#import "UIViewController+SHTransitionBlocks.h"
or
#import "SHTransitionBlocks.h"
Usage with SHNavigationControllerBlocks
So we're going to add a pan gesture to the left edge of the navigationControllers view for popping the stack. This makes it so any pushed controllers on a navigationController can pop itself with a left edge pan gesture. Nothing fancy in the animation itself.
You can always check the Sample Just download the zip and build & run.
-(void)viewDidLoad; {
[super viewDidLoad];
So can add the blocks on the first view controller on the UINavigationController stack,
viewDidLoad
will have to do here, but you can always extract it out easy (just take the entire block!)
[self.navigationController SH_setAnimationDuration:0.5 withPreparedTransitionBlock:^(UIView *containerView, UIViewController *fromVC, UIViewController *toVC, NSTimeInterval duration, id<SHViewControllerAnimatedTransitioning> transitionObject, SHViewControllerAnimationCompletionBlock transitionDidComplete) {
A lot of callback variables, lets break it down.
- containerView, the container containing both fromVC.view and toVC.view
- fromVC, the controller pushing, eg the first view controller on the navigation stack
- toVC, the controller being pushed on the stack.
- duration, of animation, as of now it's 0.5. Can also be set on a per context basis.
- transitionObject, containing the context and reverse flag.
- transitionDidComplete block, the block to call once the animation is over.
if (transitionObject.isReversed == NO) {
toVC.view.layer.affineTransform = CGAffineTransformMakeTranslation(CGRectGetWidth(toVC.view.frame), 0);
}
else {
toVC.view.layer.affineTransform = CGAffineTransformMakeTranslation(-CGRectGetWidth(toVC.view.frame), 0);
}
Notice we use the
transitionObject
to accessisReversed
flag to check for pop or push
[UIView animateWithDuration:duration delay:0 options:kNilOptions animations:^{
toVC.view.layer.affineTransform = CGAffineTransformIdentity;
if(transitionObject.isReversed) {
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformMakeTranslation(CGRectGetWidth(fromVC.view.frame), 0);
// fromView.layer.affineTransform = CGAffineTransformScale(t, 0.5, 0.5);
fromVC.view.layer.affineTransform = t;
}
else {
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformMakeTranslation(-CGRectGetWidth(fromVC.view.frame), 0);
fromVC.view.layer.affineTransform = t;
}
Nothing complicated, a normal animation. Notice we use the duration variable for UIView Animation.
} completion:^(BOOL finished) {
toVC.view.layer.affineTransform = CGAffineTransformIdentity;
fromVC.view.layer.affineTransform = CGAffineTransformIdentity;
transitionDidComplete();
}];
}];
Notice how we call transitionDidComplete(); to clean up after the block.
[self.navigationController SH_setInteractiveTransitionWithGestureBlock:^UIGestureRecognizer *(
UIScreenEdgePanGestureRecognizer *edgeRecognizer) {
edgeRecognizer.edges = UIRectEdgeLeft;
return edgeRecognizer;
We can either use the UIScreenEdgePanGestureRecognizer that is following the block or create our own and return it. Any subclass of UIGestureRecognizer will do. In our case now, we set it for UIRectEdgeLeft pans since, we're poppin'. :-)
} onGestureCallbackBlock:^void(UIViewController * controller, UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) {
UIScreenEdgePanGestureRecognizer * recognizer = (UIScreenEdgePanGestureRecognizer*)sender;
CGFloat progress = [recognizer translationInView:sender.view].x / (recognizer.view.bounds.size.width * 1.0);
progress = MIN(1.0, MAX(0.0, progress));
if (state == UIGestureRecognizerStateBegan) {
// Create a interactive transition and pop the view controller
controller.SH_interactiveTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
[(UINavigationController *)controller popViewControllerAnimated:YES];
}
else if (state == UIGestureRecognizerStateChanged) {
// Update the interactive transition's progress
[controller.SH_interactiveTransition updateInteractiveTransition:progress];
}
else if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled) {
// Finish or cancel the interactive transition
if (progress > 0.5) {
[controller.SH_interactiveTransition finishInteractiveTransition];
}
else {
[controller.SH_interactiveTransition cancelInteractiveTransition];
}
controller.SH_interactiveTransition = nil;
}
}];
A tad more complicated, but to break it down, once the pan start, we set the
controller
(in our case, the navigationController)SH_interactiveTransition
to theUIPercentDrivenInteractiveTransition
. We only do this once a pop is about to happen, never anytime else as we want theSH_interactiveTransition
to be nil if we're popping without a gesture updating it. Notice how we set it to nil once the gesture ended.
[self.navigationController SH_setAnimatedControllerBlock:^id<UIViewControllerAnimatedTransitioning>(UINavigationController *navigationController, UINavigationControllerOperation operation, UIViewController *fromVC, UIViewController *toVC) {
navigationController.SH_animatedTransition.reversed = operation == UINavigationControllerOperationPop;
return navigationController.SH_animatedTransition;
}];
We're using SHNavigationControllerBlocks here to power our animation transition here in a block instead of a delegate callback. Easier to extract out as well once you've prototyped. Blocks are easier to prototype with.
[self.navigationController SH_setInteractiveControllerBlock:^id<UIViewControllerInteractiveTransitioning>(UINavigationController *navigationController, id<UIViewControllerAnimatedTransitioning> animationController) {
return navigationController.SH_interactiveTransition;
}];
}
If we currently have no gesture being processed
navigationController.SH_interactiveTransition
will be nil, and a normal pop will occur. However if we do have a gesture being processednavigationController.SH_interactiveTransition
will not be nil, and being passed as an interactionController for processing as our gesture progress.
@protocol SHViewControllerAnimatedTransitioning <UIViewControllerAnimatedTransitioning>
@property(nonatomic,assign,getter = isReversed) BOOL reversed;
@property(nonatomic,strong) NSMutableDictionary * userInfo;
@property(nonatomic,readonly) id<UIViewControllerContextTransitioning> transitionContext;
@end
typedef void(^SHTransitionAnimationCompletionBlock)();
typedef void(^SHTransitionPreparedAnimationBlock)(UIView * containerView,
UIViewController * fromVC,
UIViewController * toVC,
NSTimeInterval duration,
id<SHViewControllerAnimatedTransitioning> transitionObject,
SHTransitionAnimationCompletionBlock transitionDidComplete
);
typedef void(^SHTransitionAnimationBlock)(id<SHViewControllerAnimatedTransitioning> transitionObject);
typedef NSTimeInterval(^SHTransitionDurationBlock)(id<SHViewControllerAnimatedTransitioning> transitionObject);
typedef UIGestureRecognizer *(^SHTransitionGestureRecognizerCreationBlock)(UIScreenEdgePanGestureRecognizer * edgeRecognizer);
typedef void(^SHTransitionCallbackGestureRecognizerBlock)(UIViewController * controller,
UIGestureRecognizer * recognizer,
UIGestureRecognizerState state,
CGPoint location
);
@interface UIViewController (SHTransitionBlocks)
@property(nonatomic,strong, setter = SH_setInteractiveTransition:) UIPercentDrivenInteractiveTransition * SH_interactiveTransition;
-(void)SH_setInteractiveTransitionWithGestureBlock:(SHTransitionGestureRecognizerCreationBlock)theGestureCreateBlock
onGestureCallbackBlock:(SHTransitionCallbackGestureRecognizerBlock)theGestureCallbackBlock;
-(id<SHViewControllerAnimatedTransitioning>)SH_animatedTransition;
-(void)SH_setAnimationDuration:(NSTimeInterval)theDuration
withPreparedTransitionBlock:(SHTransitionPreparedAnimationBlock)theBlock;
-(void)SH_setAnimatedTransitionBlock:(SHTransitionAnimationBlock)theBlock;
-(void)SH_setDurationTransitionBlock:(SHTransitionDurationBlock)theBlock;
@property(nonatomic,readonly) SHTransitionPreparedAnimationBlock SH_blockAnimationDurationWithPreparedTransition;
@property(nonatomic,readonly) SHTransitionAnimationBlock SH_blockAnimatedTransition;
@property(nonatomic,readonly) SHTransitionDurationBlock SH_blockDurationTransition;
@end
###Contact
If you end up using SHTransitionBlocks in a project, I'd love to hear about it.
email: [email protected]
twitter: @seivanheidari
SHTransitionBlocks is © 2013 Seivan and may be freely
distributed under the MIT license.
See the LICENSE.md
file.