iOS仿今日头条模态转场动画

3,046 阅读4分钟

效果图:

设置模态自定义

  1. UIModalPresentationStyle 用来设置模态的时候自定义转场动画。
typedefNS_ENUM(NSInteger, UIModalPresentationStyle) {
      UIModalPresentationFullScreen =0,//由下到上,全屏覆盖
      UIModalPresentationPageSheet,//在portrait时是FullScreen,在landscape时和FormSheet模式一样。
      UIModalPresentationFormSheet,// 会将窗口缩小,使之居于屏幕中间。在portrait和landscape下都一样,但要注意landscape下如果软键盘出现,窗口位置会调整。
      UIModalPresentationCurrentContext,//这种模式下,presented VC的弹出方式和presenting VC的父VC的方式相同。
      UIModalPresentationCustom,//自定义视图展示风格,由一个自定义演示控制器和一个或多个自定义动画对象组成。符合UIViewControllerTransitioningDelegate协议。使用视图控制器的transitioningDelegate设定您的自定义转换。
      UIModalPresentationOverFullScreen,//如果视图没有被填满,底层视图可以透过
      UIModalPresentationOverCurrentContext,//视图全部被透过
      UIModalPresentationPopover,
      UIModalPresentationNone ,
};
  1. 我们在ViewController中设置自定义模式和代理。
  // 设置代理和present样式
     self.transitioningDelegate = self;
     self.modalPresentationStyle = UIModalPresentationCustom;
     
  1. 添加手势,用来滑动dismiss上一个页面
    // 添加手势
    UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] init];
    pan.delegate = self;
    [pan addTarget:self action:@selector(panGestureRecognizerAction:)];
    self.view.tag = 10001;
    [self.view addGestureRecognizer:pan];
  1. 手势处理滑动的逻辑
- (void)panGestureRecognizerAction:(UIPanGestureRecognizer *)pan
{
    // 产生百分比
    CGFloat process = ([pan translationInView:self.view].y) / ([UIScreen mainScreen].bounds.size.height);

    process = MIN(1.0,(MAX(0.0, process)));
    
    // 开始滑动的时候
    if (pan.state == UIGestureRecognizerStateBegan)
    {
        self.interactiveTransition = [UIPercentDrivenInteractiveTransition new];
        // 触发dismiss转场动画
        [self dismissViewControllerAnimated:YES completion:nil];
        
    }
    // 手势滑动的时候
    else if (pan.state == UIGestureRecognizerStateChanged)
    {
        [self.interactiveTransition updateInteractiveTransition:process];
    }
    // 滑动手势结束或者取消
    else if (pan.state == UIGestureRecognizerStateEnded
             || pan.state == UIGestureRecognizerStateCancelled)
    {
        // 滑动百分比大于0.3完成动画
        if (process >= 0.3)
        {
            [ self.interactiveTransition finishInteractiveTransition];
        }
        else
        {
            [ self.interactiveTransition cancelInteractiveTransition];
        }
        self.interactiveTransition = nil;
    }
}
  1. 实现代理 UIViewControllerTransitioningDelegate
#pragma mark - UIViewControllerTransitioningDelegate
// 设置继承自UIPresentationController 的自定义类的属性
- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
{
    
    ZJPresentationController *presentVC = [[ZJPresentationController alloc]initWithPresentedViewController:presented presentingViewController:presenting];
    // 设置距离顶部的高度
    presentVC.height = STATUSBAR_H;
    
    return presentVC;
}

//控制器创建执行的动画(返回一个实现UIViewControllerAnimatedTransitioning协议的类)
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
    
    //创建实现UIViewControllerAnimatedTransitioning协议的类(命名为AnimatedTransitioning)
    ZJPresentAnimation *animation = [[ZJPresentAnimation alloc] init];
    
    //将其状态改为出现
    animation.presented = YES;
    
    return animation;
}

// 控制器销毁执行的动画(返回一个实现UIViewControllerAnimatedTransitioning协议的类)
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
    
    ////创建实现UIViewControllerAnimatedTransitioning协议的类(命名为AnimatedTransitioning)
    ZJPresentAnimation *animation = [[ZJPresentAnimation alloc] init];
    
    //将其状态改为出现
    animation.presented = NO;
    
    return animation;
}

// 返回一个交互的对象(实现UIViewControllerInteractiveTransitioning协议的类)
-(id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator{
    return self.interactiveTransition;
}

自定义转成动画

  1. 实现UIViewControllerAnimatedTransitioning协议,包含了动画的时间和具体的实现。
// 自定义转场动画时间
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
    return 0.34f;
}


// 自定义转成动画具体实现
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    
    // 到哪个vc
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    // 从哪个vc来
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    // 获取过渡时候的容器view
    UIView *contentView  = [transitionContext containerView];;
    
    
    // 移除之前的
    for (UIView *subView in [contentView subviews]) {
        if (subView.tag == 1001 ) {
            [subView removeFromSuperview];
        }
    }
    
    UIView * transView = nil;
    // 如果是present
    if (_presented) {
        // 记录present的view
        transView = toViewController.view;
        
    }
    else {
        transView = fromViewController.view;
    }
    
    // y值
    CGFloat y = transView.frame.origin.y;
    transView.frame = CGRectMake(0, _presented ?SCREEN_H :y, SCREEN_W, SCREEN_H);
    
    // 黑色背景
    UIView  *viewCover = [[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_W, SCREEN_H)];
    viewCover.backgroundColor = [UIColor blackColor];
    viewCover.alpha = 0.5;
    viewCover.tag = 1001;
    [contentView insertSubview:viewCover belowSubview:transView];
    
    if (_presented) {
        
        // 执行动画过程
        [UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:2 options:UIViewAnimationOptionLayoutSubviews animations:^{
            transView.frame = CGRectMake(0, y , SCREEN_W, SCREEN_H);
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
        }];
        
    }
    else
    {
        // 下拉动画
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
            transView.frame = CGRectMake(0, SCREEN_H, SCREEN_W, SCREEN_H);
            viewCover.alpha= 0;
        } completion:^(BOOL finished) {
            if (!transitionContext.transitionWasCancelled) {
                [viewCover removeFromSuperview];
            }
            
            [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
        }];
    }
}

手势冲突的处理

  1. 弹出的ViewController里面包含UITableView的话,在滑动的顶部的时候想要触发滑动dismiss的手势的处理方式。
#pragma mark --UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    return YES;
}


- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    // 支持多手势
    return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0)
{
    // 这个方法返回YES,第一个手势和第二个互斥时,第一个会失效
    if ([otherGestureRecognizer.view isKindOfClass:[UITableView class]]) {
        return YES;
    }
    return NO;
}

  1. 在处理UITableView滑动到顶部时候的逻辑
#pragma mark --UIScrollViewDelegate
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    [self setGestureRecognizerEnable:YES scrollView:scrollView];
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self setGestureRecognizerEnable:YES scrollView:scrollView];
}

-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    // 结束的时候手势可用
    [self setGestureRecognizerEnable:YES scrollView:scrollView];
    
}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    // 滑动到顶部的时候设置手势失效
    if (scrollView.contentOffset.y <=0) {
        
        [self setGestureRecognizerEnable:NO scrollView:scrollView];
    }
    
    else
    {
        // 手势可用
        [self setGestureRecognizerEnable:YES scrollView:scrollView];
    }
}

-(void)setGestureRecognizerEnable:(BOOL)isEnable scrollView:(UIScrollView *)scrollView
{
    for (UIGestureRecognizer *gesRec in  scrollView.gestureRecognizers) {
        if ([gesRec isKindOfClass:[UIPanGestureRecognizer class]]) {
            
            gesRec.enabled =isEnable;
        }
    }
}

处理方式是滑动到顶部的时候,禁用UITableview的手势,然后触发当前ViewController中的手势滑动dismiss会上一个页面的手势。

DEMO地址