iOS7新特性 ViewController转场切换(三) 自定义视图控制器容器的切换---非交互式(转)

@继续前面的内容,这一章,主要介绍自定义ViewController容器上视图VC的切换.先来看看系统给我们提供的容器控制器 UINavigationController和UITabBarController 都有一个NSArray类型的属性viewControllers,很明显,存储的就是需要切换的视图VC.同理,我们定义一个ContainerViewController,是UIViewController的直接子类,用来作为容器依托,额,其他属性定义详见代码吧,这里不多说了.(PS:原先我进行多个自定义视图VC切换的方法,是放置一个UIScrollView,然后把所有childViewController的View的frame的X坐标,依此按320递增,大家可以自行想象下,这样不好的地方,我感觉就是所有的VC一经加载就全部实体化了,而且不会因为被切换变成暂不显示而释放掉)

偷懒下,用storyboard创建的5个childVC

// ContainerViewController  
@interface FTContainerViewController ()   

@property (strong, nonatomic) FTPhotoSenderViewController   *photoSenderViewController;  
@property (strong, nonatomic) FTVideoSenderViewController   *videoSenderViewController;  
@property (strong, nonatomic) FTFileSenderViewController      *fileSenderViewController;  
@property (strong, nonatomic) FTContactSenderViewController     *contactSenderViewController;  
@property (strong, nonatomic) FTClipboardSenderViewController   *clipboardSenderViewController;  
@property (strong, nonatomic) UIViewController                  *selectedViewController;        // 当前选择的VC  
@property (strong, nonatomic) NSArray                           *viewControllers;               // childVC数组  
@property (assign, nonatomic) NSInteger                         currentControllerIndex;         // 当前选择的VC的数组下标号  

@end  

@implementation FTContainerViewController  

#pragma mark - ViewLifecycle Methods  

- (void)viewDidLoad  
{  
[super viewDidLoad];  
// childVC  
self.photoSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTPhotoSenderViewController"];  
self.videoSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTVideoSenderViewController"];  
self.fileSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTFileSenderViewController"];  
self.contactSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTContactSenderViewController"];  
self.clipboardSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTClipboardSenderViewController"];  
// 存储childVC的数组  
self.viewControllers = @[_photoSenderViewController,_videoSenderViewController,_fileSenderViewController,_contactSenderViewController,_clipboardSenderViewController];  
// 缺省为下标为0的VC  
self.selectedViewController = self.selectedViewController ?: self.viewControllers[0];  
self.currentControllerIndex = 0;  
}  

依旧,实现UIViewControllerAnimatedTransitioning协议的Animator类,不过里面换个动画效果,利用iOS7新增的弹簧动画效果:

#import "FTMthTransitionAnimator.h"  

@implementation FTMthTransitionAnimator  

static CGFloat const kChildViewPadding = 16;  
static CGFloat const kDamping = 0.5;    // damping参数代表弹性阻尼,随着阻尼值越来越接近0.0,动画的弹性效果会越来越明显,而如果设置阻尼值为1.0,则视图动画不会有弹性效果
static CGFloat const kInitialSpringVelocity = 0.5;  // 初始化弹簧速率  
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext  
{  
return 1.0;  
}  
   
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext  
  {  
/**
 * - viewControllerForKey:我们可以通过他访问过渡的两个 ViewController。 
 *  - containerView:两个 ViewController 的 containerView。 
 *  - initialFrameForViewController 和 finalFrameForViewController 是过渡开始和结束时每个 ViewController 的 frame。 
 */  
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];  
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];  
  
[[transitionContext containerView] addSubview:toViewController.view];  
toViewController.view.alpha = 0;  
BOOL goingRight = ([transitionContext initialFrameForViewController:toViewController].origin.x < [transitionContext finalFrameForViewController:toViewController].origin.x);  
CGFloat transDistance = [transitionContext containerView].bounds.size.width + kChildViewPadding;  
CGAffineTransform transform = CGAffineTransformMakeTranslation(goingRight ? transDistance : -transDistance, 0);  
// CGAffineTransformInvert 反转  
toViewController.view.transform = CGAffineTransformInvert(transform);  
// toViewController.view.transform = CGAffineTransformTranslate(toViewController.view.transform, (goingRight ? transDistance : -transDistance), 0);  
  
/** 
 *   ----------弹簧动画.....------- 
 *  使用由弹簧的运动描述的时序曲线` animations` 。当` dampingRatio`为1时,动画将平稳减速到其最终的模型值不会振荡。阻尼比小于1来完全停止前将振荡越来越多。可以使用弹簧的初始速度,以指定的速度在模拟弹簧的端部的物体被移动它附着之前。这是一个单元坐标系,其中1是指行驶总距离的动画在第二。所以,如果你改变一个物体的位置由200PT在这个动画,以及你想要的动画表现得好像物体在动,在100PT /秒的动画开始之前,你会通过0.5 。你通常会想通过0的速度。 
 */  
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:kDamping initialSpringVelocity:kInitialSpringVelocity options:0x00 animations:^{  
    fromViewController.view.transform = transform;  
    fromViewController.view.alpha = 0;  
    // CGAffineTransformIdentity  重置,初始化  
    toViewController.view.transform = CGAffineTransformIdentity;  
    toViewController.view.alpha = 1;  
} completion:^(BOOL finished) {  
    fromViewController.view.transform = CGAffineTransformIdentity;  
    // 声明过渡结束-->记住,一定别忘了在过渡结束时调用 completeTransition: 这个方法。  
    [transitionContext completeTransition:![transitionContext transitionWasCancelled]];  
}];  
}  

 @end  

接下来的代码,就是实现自定义容器切换的关键了.通常情况下,当我们使用系统内建的类时,系统框架为我们创建了转场上下文对象,并把它传递给动画控制器。但是在我们这种情况下,我们需要自定义转场动画,所以我们需要承担系统框架的责任,自己去创建这个转场上下文对象。


@interface FTMthTransitionContext : NSObject <UIViewControllerContextTransitioning>  

- (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight;  
@property (nonatomic, copy) void (^completionBlock)(BOOL didComplete);  
@property (nonatomic, assign, getter=isAnimated) BOOL animated;  
@property (nonatomic, assign, getter=isInteractive) BOOL interactive; // 是否交互式    
@property (nonatomic, strong) NSDictionary *privateViewControllers;  
@property (nonatomic, assign) CGRect privateDisappearingFromRect;  
@property (nonatomic, assign) CGRect privateAppearingFromRect;  
@property (nonatomic, assign) CGRect privateDisappearingToRect;  
@property (nonatomic, assign) CGRect privateAppearingToRect;  
@property (nonatomic, weak) UIView *containerView;  
@property (nonatomic, assign) UIModalPresentationStyle presentationStyle;  

@end  

@implementation FTMthTransitionContext  

- (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight {  
  
if ((self = [super init])) {  
    self.presentationStyle = UIModalPresentationCustom;  
    self.containerView = fromViewController.view.superview;  
    self.privateViewControllers = @{  
                                    UITransitionContextFromViewControllerKey:fromViewController,  
                                    UITransitionContextToViewControllerKey:toViewController,  
                                    };  
      
    // Set the view frame properties which make sense in our specialized ContainerViewController context. Views appear from and disappear to the sides, corresponding to where the icon buttons are positioned. So tapping a button to the right of the currently selected, makes the view disappear to the left and the new view appear from the right. The animator object can choose to use this to determine whether the transition should be going left to right, or right to left, for example.  
    CGFloat travelDistance = (goingRight ? -self.containerView.bounds.size.width : self.containerView.bounds.size.width);  
    self.privateDisappearingFromRect = self.privateAppearingToRect = self.containerView.bounds;  
    self.privateDisappearingToRect = CGRectOffset (self.containerView.bounds, travelDistance, 0);  
    self.privateAppearingFromRect = CGRectOffset (self.containerView.bounds, -travelDistance, 0);  
}  
  
return self;  
}  

- (CGRect)initialFrameForViewController:(UIViewController *)viewController {  
if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {  
    return self.privateDisappearingFromRect;  
} else {  
    return self.privateAppearingFromRect;  
}  
}  

- (CGRect)finalFrameForViewController:(UIViewController *)viewController {  
if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {  
    return self.privateDisappearingToRect;  
} else {  
    return self.privateAppearingToRect;  
}  
}  

- (UIViewController *)viewControllerForKey:(NSString *)key {  
return self.privateViewControllers[key];  
}  

 - (void)completeTransition:(BOOL)didComplete {  
if (self.completionBlock) {  
    self.completionBlock (didComplete);  
}  
}  

// 非交互式,直接返回NO,因为不允许交互当然也就无法操作进度取消  
- (BOOL)transitionWasCancelled { return NO; }  

// 非交互式,直接不进行操作,只有进行交互,下面3个协议方法才有意义,可参照系统给我们定义好的交互控制器
//@interface UIPercentDrivenInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>
- (void)updateInteractiveTransition:(CGFloat)percentComplete {}  
- (void)finishInteractiveTransition {}  
- (void)cancelInteractiveTransition {}  

@end 

OK,准备工作都做好了,为了仿照UIScrollView的滑动切换,但又因为现在展示的是非交互式,我们定义一个swip(轻扫)手势.

UISwipeGestureRecognizer *leftGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swapController:)];  
[leftGesture setDirection:UISwipeGestureRecognizerDirectionLeft];  
[self.view addGestureRecognizer:leftGesture];  

UISwipeGestureRecognizer *rightGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swapController:)];  
[rightGesture setDirection:UISwipeGestureRecognizerDirectionRight];  
[self.view addGestureRecognizer:rightGesture];  

// 响应手势的方法  
- (void)swapViewControllers:(UISwipeGestureRecognizer *)swipeGestureRecognizer  
 {  
    if (swipeGestureRecognizer.direction == UISwipeGestureRecognizerDirectionLeft) {  
    if (_currentControllerIndex < 4) {  
        _currentControllerIndex++;  
    }  
    NSLog(@"_currentControllerIndex = %ld",(long)_currentControllerIndex);  
    UIViewController *selectedViewController = self.viewControllers[_currentControllerIndex];  
     NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"右边");  
     self.selectedViewController = selectedViewController;  
} else if (swipeGestureRecognizer.direction == UISwipeGestureRecognizerDirectionRight){  
     NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"左边");  
    if (_currentControllerIndex > 0) {  
        _currentControllerIndex--;  
    }  
    UIViewController *selectedViewController = self.viewControllers[_currentControllerIndex];  
    self.selectedViewController = selectedViewController;  
}  
}  

// 重写selectedViewController的setter  
- (void)setSelectedViewController:(UIViewController *)selectedViewController  
{  
     NSParameterAssert (selectedViewController);  
    [self _transitionToChildViewController:selectedViewController];  
     _selectedViewController = selectedViewController;  
}  

   // 切换操作(自定义的,联想我在前面文章网易标签栏切换中,系统给的transitionFromViewController,是一个道理)  
- (void)_transitionToChildViewController:(UIViewController *)toViewController  
{  
  UIViewController *fromViewController = self.childViewControllers.count > 0 ? self.childViewControllers[0] : nil;  
  if (toViewController == fromViewController) {  
    return;  
  }  
  UIView *toView = toViewController.view;  
  [toView setTranslatesAutoresizingMaskIntoConstraints:YES];  
  toView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;  
  toView.frame = self.view.bounds;  
  
// 自定义容器的切换,addChildViewController是关键,它保证了你想要显示的VC能够加载到容器中  
// 而所谓的动画和上下文,只是为了转场的动画效果  
// 因此,就算用UIScrollView切换,也不能缺少addChildViewController,切记!切记!  
  [fromViewController willMoveToParentViewController:nil];  
  [self addChildViewController:toViewController];  
  
  if (!fromViewController) {  
    [self.view addSubview:toViewController.view];  
    [toViewController didMoveToParentViewController:self];  
    return;  
  }  
  
// Animator  
  FTMthTransitionAnimator *transitionAnimator = [[FTMthTransitionAnimator alloc] init];  
  NSUInteger fromIndex = [self.viewControllers indexOfObject:fromViewController];  
  NSUInteger toIndex = [self.viewControllers indexOfObject:toViewController];  
  
// Context  
  FTMthTransitionContext *transitionContext = [[FTMthTransitionContext alloc] initWithFromViewController:fromViewController toViewController:toViewController goingRight:(toIndex > fromIndex)];  
  transitionContext.animated = YES;  
  transitionContext.interactive = NO;  
  transitionContext.completionBlock = ^(BOOL didComplete) {  
    // 因为是非交互式,所以fromVC可以直接直接remove出its parent's children controllers array  
    [fromViewController.view removeFromSuperview];  
    [fromViewController removeFromParentViewController];  
    [toViewController didMoveToParentViewController:self];  
    if ([transitionAnimator respondsToSelector:@selector (animationEnded:)]) {  
        [transitionAnimator animationEnded:didComplete];  
    }  
};  
// 转场动画需要以转场上下文为依托,因为我们是自定义的Context,所以要手动设置  
[transitionAnimator animateTransition:transitionContext];  
}  

大功告成.
** 上面展示的就是一个基本的自定义容器的非交互式的转场切换.那交互式的呢?从上面我定义手势定义为swip而不是pan也可以看出,非交互转场,并不能完全实现UIScrollView那种分页式的效果,按照类似百分比的形式来进行fromVC和toVC的切换,因为我们缺少交互控制器.在自定义的容器中,系统是没有提供返回交互控制器的协议给我们的,查了蛮多资料,也没找到给出明确的方法,我认为,要跟实现转场上下文一样,仿照系统方法,自定义的去实现交互式的协议方法.我们就要去思考,系统是如何搭建起这个环境的.**
** 容后续给出响应的Demo,目前研究中.......**
** @转载请注明:iOS@迷糊小书童**

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容