iOS 8 自定义视图控制器演示:在动画期间更改呈现的 VC 的大小

Posted

技术标签:

【中文标题】iOS 8 自定义视图控制器演示:在动画期间更改呈现的 VC 的大小【英文标题】:iOS 8 custom view controller presentation: changing size of presented VC during animation 【发布时间】:2014-08-26 00:52:09 【问题描述】:

我正在编写一个在表格视图中包含一系列卡片的应用程序,类似于启用 Google Now 卡片时适用于 ios 的 Google 应用程序的布局。当用户点击一张卡片时,应该有一个自定义的过渡到一个新的视图控制器,基本上卡片看起来更大,几乎填满了屏幕,并且上面有更多的细节。自定义转换本身应该看起来像卡片向上动画并增大大小,直到达到最终大小和位置,现在是持有卡片的新视图控制器。

我一直在尝试使用自定义视图控制器转换来解决此问题。当卡片被点击时,我使用UIModalPresentationCustom 启动一个自定义视图控制器转换,并设置一个转换委托,它本身提供一个自定义动画师和一个自定义 UIPresentationController。在animateTransition: 中,我将新视图控制器的视图添加到容器视图中,最初将框架设置为卡片的框架(因此看起来卡片仍然存在且未更改)。然后我尝试执行一个动画,其中呈现的视图的框架尺寸变大并改变位置,使其移动到最终位置。

这是我上面描述的一些代码 - 我试图让它简短而甜蜜,但如果需要我可以提供更多信息:

过渡代表

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext 

    // NOWAnimationDelegate is my own custom protocol which defines the method for asking the presenting VC for the tapped card's frame.
    UIViewController<NOWAnimationDelegate> *fromVC = (UIViewController<NOWAnimationDelegate> *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *finalVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];

    // Ask the presenting view controller for the frame of the tapped card - this method works.
    toView.frame = [fromVC rectForSelectedCard];

    [transitionContext.containerView addSubview:toView];

    CGRect finalRect = [transitionContext finalFrameForViewController:finalVC];

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^
        toView.frame = finalRect;
    completion:^(BOOL finished) 
        [transitionContext completeTransition:YES];
    ];

自定义 UIPresentationController

-(CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize 
    return CGSizeMake(0.875*parentSize.width, 0.875*parentSize.height);


-(CGRect)frameOfPresentedViewInContainerView 
    CGRect presentedViewFrame = CGRectZero;
    CGRect containerBounds = self.containerView.bounds;

    presentedViewFrame.size = [self sizeForChildContentContainer:(UIView<UIContentContainer> *)self.presentedView withParentContainerSize:containerBounds.size];
    presentedViewFrame.origin.x = (containerBounds.size.width - presentedViewFrame.size.width)/2;
    presentedViewFrame.origin.y = (containerBounds.size.height - presentedViewFrame.size.height)/2 + 10;

    return presentedViewFrame;

我发现正在发生的是新视图在动画开始时自动立即设置为其最终大小,然后动画只是向上动画的新视图。使用断点,我注意到在 [transitionContext.containerView addSubview:toView] 调用期间调用了 frameOfPresentedViewInContainerView,这可能可以解释为什么会发生这种情况 - frameOfPresentedViewInContainerView 返回“在动画结束时分配给呈现视图的框架矩形”根据 UIPresentationController 文档。

但是,我不确定如何进行,或者是否真的有可能。我见过的所有自定义视图控制器转换的示例都具有呈现的视图控制器的最终大小,并且在动画期间保持不变。有什么方法可以在动画期间通过改变呈现视图的大小来执行自定义视图控制器转换,还是我必须以不同的方式处理这个?

【问题讨论】:

【参考方案1】:

基本上,您需要做的是在转换委托中使用 CGAffineTransform 为您的 toView.transform 设置动画。步骤:

    在动画之前设置你的 toView.frame = 你的帧,当它不显示时 比方说,创建 CGAffineTransformMakeScale 以从隐藏帧缩放到所需的最终呈现帧 在动画块上设置为 View.transform 到您在步骤 2 中创建的变换

【讨论】:

感谢您的回答。我以前没有使用过 CGAffineTransform,但听起来它会随着时间的推移缩放帧(例如,从其大小的 50% 到全尺寸)。就像一个小矩形长成一个具有相同纵横比的大矩形。但它是否适用于我希望在过渡期间宽度不变但高度确实发生变化的情况,从而随着内容的增长而显示内容?这就是我正在寻找的那种转变。 只需将动画之前的帧宽度设置为等于最终宽度并将比例设置为0,也许? @AdityaWirayudha Scale 在这种情况下必须为 1,因为它是通过乘法改变大小的因素。【参考方案2】:

正如 Aditya 提到的那样,CGAffineTransform 是通往这里的道路。

我现在可以保持宽度了:

    CGFloat targetscale=initialHeight/finalHeight;
    CGFloat targetyoffset=(finalHeight-(finalHeight*targetscale))/2;
    int targety=roundf(initialPosition-targetyoffset);

    CGAffineTransform move=CGAffineTransformMakeTranslation(0, targety);
    CGAffineTransform scale=CGAffineTransformMakeScale(1,targetscale);
    toView.transform=CGAffineTransformConcat(scale,move);

这会考虑确定的比例来定位传入的视图,然后同时执行两个变换,以便视图缩放并移动到最终位置和大小。

然后你就设置

toView.transform=CGAffineTransformIdentity;

在动画块中,它将缩放到最终位置和大小。

注意,这只会在垂直维度上移动和缩放,但可以适应所有方向的缩放,如下所示:

+(CGAffineTransform)transformView:(UIView*)view toTargetRect:(CGRect)rect

  CGFloat targetscale=rect.size.height/view.frame.size.height;

  CGFloat targetxoffset=(view.frame.size.width-(view.frame.size.width*targetscale))/2;
  int targetx=roundf(rect.origin.x-view.frame.origin.x-targetxoffset);

  CGFloat targetyoffset=(view.frame.size.height-(view.frame.size.height*targetscale))/2;
  int targety=roundf(rect.origin.y-view.frame.origin.y-targetyoffset);

  CGAffineTransform move=CGAffineTransformMakeTranslation(targetx, targety);
  CGAffineTransform scale=CGAffineTransformMakeScale(targetscale,targetscale);

  return CGAffineTransformConcat(scale, move);

并且不要忘记将单元格的矩形转换为全局坐标空间,以便起始帧对于整个窗口都是正确的,而不仅仅是表格中的单元格位置。

CGRect cellRect=[_tableView rectForRowAtIndexPath:[_tableView indexPathForSelectedRow]];

cellRect=[self.tableView convertRect:cellRect toView:presenting.view];

希望这会有所帮助!

【讨论】:

【参考方案3】:

Apple 提供了一个 wwdc 示例应用程序“LookInside”,并附有谈话 228:“查看演示控制器”。它具有 2 个自定义演示文稿,其中一个动画呈现的视图控制器的大小。看看里面的代码应该可以帮助你;)

【讨论】:

如果它仍然有效,它可能会对某人有所帮助。在修复了我假设是对新 API 的后期更改的代码中的一些问题之后,它找不到 plist 文件。 希望提供更多帮助。 @ChrisPrince - 此版本编译:developer.apple.com/library/ios/samplecode/LookInside/… 那个苹果示例项目没有做他所追求的,因为它只是从屏幕中心缩放 vc

以上是关于iOS 8 自定义视图控制器演示:在动画期间更改呈现的 VC 的大小的主要内容,如果未能解决你的问题,请参考以下文章

自定义 inputView 在模态演示期间未正确设置动画

在视图控制器演示动画期间响应触摸事件

如何在尺寸类更改期间从自定义演示控制器过渡到模态?

更改导航控制器 ios 的转换

iOS - 创建自定义过渡动画

UIScrollView 在模态视图控制器的动画演示中滚动到不同的位置