【问题标题】:Expanding circle animated transition between view controllers在视图控制器之间展开圆形动画过渡
【发布时间】:2014-09-02 12:48:57
【问题描述】:

我使用转换委托创建了一个自定义动画转换:

vc.modalTransitionStyle = UIModalPresentationCustom;
vc.transitioningDelegate = self.transitioningDelegate;

过渡代理运行良好,但我正在尝试实现迄今为止尚未成功的效果。 我正在尝试让新的视图控制器看起来像这样:

Cool animated tranisitoning

我尝试过 CGAffineTransformMakeScale,但它会缩放新的视图控制器视图,而不是仅仅“将视野扩大到其中”。当试图用动画改变边界和框架时,它总是使最后的圆圈与屏幕边界相匹配,并且不会像我给出的动画那样完全打开圆圈。

有什么想法吗?

谢谢。

【问题讨论】:

  • 这只是一个猜测,但您可以尝试使用 png 作为蒙版 (stackoverflow.com/questions/4559743/how-to-mask-uiviews-in-ios),然后可能会为蒙版设置动画?
  • 感谢@leparlon,但如果我要转换到的视图控制器看起来并不总是一样,是否也有可能?
  • 是的,这可以通过遮罩层实现。因为它只定义了目标视图控制器的哪一部分是可见的,所以它适用于任何视图控制器,无论它们看起来如何。

标签: ios uiviewanimation uiviewanimationtransition


【解决方案1】:

我对这个问题的第一个答案是我不久前制作的应用程序的修改版本,现在不是最好的方法(iOS 7 及更高版本)。在 iOS 7 中,我们可以使用 UIViewControllerTransitioningDelegate 以及相关的委托和类来创建自定义转换。此版本将所有蒙版创建和调整大小封装在 animator 对象(符合 UIViewControllerAnimatedTransitioning 的那个)中,因此它使控制器代码更简单,允许您使用任何控制器作为目标控制器而无需特殊代码。这是我在进行演示的课程中使用的代码,

#import "ViewController.h"
#import "SecondViewController.h"
#import "RDPresentationAnimator.h"

@interface ViewController () <UIViewControllerTransitioningDelegate>
@property (weak,nonatomic) IBOutlet UIButton *button;
@end

@implementation ViewController

-(IBAction)presentSecondController:(UIButton *)sender {
    self.button = sender;
    SecondViewController *second = [self.storyboard instantiateViewControllerWithIdentifier:@"Second"];
    second.modalTransitionStyle = UIModalPresentationCustom;
    second.transitioningDelegate = self;
    [self presentViewController:second animated:YES completion:nil];
}

-(id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    RDPresentationAnimator *animator = [RDPresentationAnimator new];
    animator.isPresenting = YES;
    animator.senderFrame = self.button.frame;
    return animator;
}


-(id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    NSLog(@"dismissed delegate called");
    RDPresentationAnimator *animator = [RDPresentationAnimator new];
    animator.isPresenting = NO;
    animator.senderFrame = self.button.frame;
    return animator;
}

这是动画对象中的代码。 .h,

@interface RDPresentationAnimator : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic) BOOL isPresenting;
@property (nonatomic) CGRect senderFrame;

这是.m,

@interface RDPresentationAnimator ()
@property (strong,nonatomic) UIBezierPath *maskPath;
@property (strong,nonatomic) UIViewController *toVC;
@property (strong,nonatomic) UIViewController *fromVC;
//@property (strong,nonatomic) id <UIViewControllerContextTransitioning> transitionContext;
@end

    @implementation RDPresentationAnimator

    #define ANIMATION_TIME 0.6



    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
        return ANIMATION_TIME;
    }


    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
       // self.transitionContext = transitionContext;
        self.fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        self.toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

        if (self.isPresenting) {
            [transitionContext.containerView addSubview:self.fromVC.view];
            [transitionContext.containerView addSubview:self.toVC.view];

            self.maskPath = [UIBezierPath bezierPathWithOvalInRect:self.senderFrame];
            CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
            maskLayer.frame = self.toVC.view.frame;
            maskLayer.path = self.maskPath.CGPath;
            self.toVC.view.layer.mask = maskLayer;

            UIBezierPath *newPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-self.toVC.view.frame.size.width/2, -self.toVC.view.frame.size.height/2, self.toVC.view.frame.size.height*2, self.toVC.view.frame.size.height*2)];
            CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath: @"path"];
            //pathAnim.delegate = self;
            pathAnim.fromValue = (id)self.maskPath.CGPath;
            pathAnim.toValue = (id)newPath.CGPath;
            pathAnim.duration = ANIMATION_TIME;
            maskLayer.path = newPath.CGPath;
            [maskLayer addAnimation:pathAnim forKey:@"path"];
        }else{
            [transitionContext.containerView addSubview:self.toVC.view];
            [transitionContext.containerView addSubview:self.fromVC.view];
            self.maskPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-self.fromVC.view.frame.size.width/2, -self.fromVC.view.frame.size.height/2, self.fromVC.view.frame.size.height*2, self.fromVC.view.frame.size.height*2)];
            CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
            maskLayer.frame = self.fromVC.view.frame;
            maskLayer.path = self.maskPath.CGPath;
            self.fromVC.view.layer.mask = maskLayer;

            UIBezierPath *newPath = [UIBezierPath bezierPathWithOvalInRect:self.senderFrame];
            CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath: @"path"];
            //pathAnim.delegate = self;
            pathAnim.fromValue = (id)self.maskPath.CGPath;
            pathAnim.toValue = (id)newPath.CGPath;
            pathAnim.duration = ANIMATION_TIME;
            maskLayer.path = newPath.CGPath;
            [maskLayer addAnimation:pathAnim forKey:@"path"];
        }

        [self performSelector:@selector(finishTransition:) withObject:transitionContext afterDelay:ANIMATION_TIME];
    }



    -(void)finishTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
        [transitionContext completeTransition:YES];
    }


    //- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    //    NSLog(@"In didStop");
    //    if (flag) {
    //        if (self.isPresenting) {
    //            self.toVC.view.layer.mask = nil;
    //            [self.transitionContext completeTransition:YES];
    //        }else{
    //            self.fromVC.view.layer.mask = nil;
    //            [self.transitionContext completeTransition:YES];
    //            self.transitionContext = nil;
    //        }
    //    }
    //}


    -(void)dealloc {
        NSLog(@"In animator dealloc");
    }

当您关闭模态控制器时,此代码还具有反向动画(您只需像在该类中一样调用dismissViewControllerAnimated:completion:)。

【讨论】:

  • @Yarneo 在对我发布的代码进行了一些试验后,我发现存在某种保留周期,导致动画师和呈现的控制器永远不会被释放。我已经编辑了我的代码来解决这个问题。我显示所有我删除的行都被注释掉了。我不再为 transitionContext 使用属性,也不再为 pathAnim 使用委托。相反,我在一个基于动画时间的延迟调用方法中调用 completeTransition。
  • 感谢样本,它工作正常。我会编辑答案,因为在您为 fromVC 和 toVC 的每个步骤添加的容器视图中,它会产生问题,因为容器在完成后释放,我删除了 addSubview,只有这个是必要的 [transitionContext.containerView addSubview:self.toVC 。看法];并在关闭时修复黑屏。
  • 但圆圈先覆盖右侧,然后覆盖左侧
【解决方案2】:

我过去做过类似的东西,但它没有那个扩大的橙色环。我的 InitialViewController 在情节提要中添加了一个按钮,该按钮具有圆形遮罩和背景图像。我呈现的视图控制器有一个掩码,它的大小和位置是通过传入 InitialViewController 中触发呈现的按钮的框架来设置的。呈现的视图控制器定义了一个协议,其一个方法在动画结束时被调用。这是初始视图控制器中的代码,

#import "InitialViewController.h"
#import "ViewController.h"

@interface InitialViewController () <ExpandingViewDelegate>
@end

@implementation InitialViewController


-(IBAction)expandToDetail:(UIButton *)sender { 
    ViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:@"VC"];
    vc.delegate = self;
    vc.buttonRect = sender.frame;
    [self.view addSubview:vc.view];
}


-(void)viewFinishedAnimating:(UIViewController *) sender { // delegate method from the presentedViewController
    [sender.view removeFromSuperview];
    [self presentViewController:sender animated:NO completion:nil];
}

这是呈现的视图控制器中的代码,它执行掩码扩展。 .h 文件,

@protocol ExpandingViewDelegate <NSObject>
-(void)viewFinishedAnimating:(UIViewController *) sender;
@end

@interface ViewController : UIViewController

@property (nonatomic) CGRect buttonRect;
@property (weak,nonatomic) id<ExpandingViewDelegate> delegate;
@end

.m 文件,

@interface ViewController ()
@property (strong,nonatomic) UIBezierPath *maskPath;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.maskPath = [UIBezierPath bezierPathWithOvalInRect:self.buttonRect];
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = self.view.frame;
    maskLayer.path = self.maskPath.CGPath;
    self.view.layer.mask = maskLayer;
    [self enlargeMask:maskLayer];
}

-(void)enlargeMask:(CAShapeLayer *) shapeLayer {

    UIBezierPath *newPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-self.view.frame.size.width/2, -self.view.frame.size.height/2, self.view.frame.size.height*2, self.view.frame.size.height*2)];
    CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath: @"path"];
    pathAnim.delegate = self;
    pathAnim.fromValue = (id)self.maskPath.CGPath;
    pathAnim.toValue = (id)newPath.CGPath;
    pathAnim.duration = 6;
    shapeLayer.path = newPath.CGPath;
    [shapeLayer addAnimation:pathAnim forKey:@"path"];
}

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    if (flag) {
        self.view.layer.mask = nil;
        [self.delegate viewFinishedAnimating:self];
    }
}

【讨论】:

  • 非常感谢!只有一件事,我注意到一旦调用了委托函数,然后调用了 presentviewcontroller 方法,我的屏幕就会冻结,我无法再与它交互。另一方面,如果我不调用 presentviewcontroller,屏幕能够接收用于滚动等的点击,但是我按下任何按钮,它都会立即崩溃。知道为什么屏幕会因此而冻结吗?
  • @Yarneo,不,我不知道为什么会这样。你确定你完全按照我写的代码实现了,因为我已经测试过了,它不会冻结。
  • 照原样复制。注意到 [uiviewcontroller nextresponder] 有不间断的分配,认为它以某种方式连接。
  • @Yarneo,你在故事板中有什么设置?
  • @Yarneo,如果控制器不是初始控制器,我很困惑为什么会出现这个错误,但我找到了一个修复(至少对于我的简单案例),那就是删除在调用 presentViewController 之前呈现的视图控制器的视图来自其父视图。我已经编辑了我的答案;我在 InitialViewController 中为 viewFinishedAnimating: 的实现添加了一行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-01
  • 1970-01-01
相关资源
最近更新 更多