【问题标题】:Workaround for custom UIViewController animations in landscape?横向自定义 UIViewController 动画的解决方法?
【发布时间】:2013-11-16 02:32:04
【问题描述】:

我有一个自定义的动画 UIViewController 过渡,并且似乎 iOS 中存在一个错误,该错误会破坏横向布局。在主要动画方法中,我得到了横向和纵向视图的混合。 (纵向视图都是纵向的,所以没问题。)

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
  UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  UIView *containerView = [transitionContext containerView];

  // fromViewController.view => landscape, transform
  // toViewController.view => portrait, transform
  // containerView => portrait, no transform

  [containerView addSubview:toViewController.view];

  // ...animation... //
}

我知道frame 属性在视图具有转换时不可靠 - 所以我猜这是问题的根源。在横向模式下,to/from viewControllers 视图具有 90 度顺时针变换 [0 -1 1 0]。我尝试使用 bounds/center 来调整视图的大小和位置,以及移除变换然后重新应用它,但 UIKit 与我抗争并坚持将视图显示为纵向。烦人!

截图中,深灰色为 UIWindow 背景,红色为添加的模态视图控制器,应该覆盖整个屏幕。

有人找到解决方法吗?

【问题讨论】:

  • 嗨,Jason,我遇到这个问题已经有一段时间了,我真的不认为接受的答案是一个可以接受的解决方案,因为它看起来很老套,而且我还没有让它在 iOS 上工作7.1。话虽如此,我已经回答了这个问题,以便在没有黑客攻击的情况下正确解决这个问题。如果这有帮助,请告诉我

标签: uiviewcontroller ios7 modalviewcontroller


【解决方案1】:

好的,修复非常简单:

将 toViewController 框架设置到容器之前将视图添加到容器。

toViewController.view.frame = containerView.frame;
[containerView addSubview:toViewController.view];

更新:仍有一个限制,您不知道框架的方向。它最初是纵向的,但是当它显示在屏幕上时会拉伸成横向。如果您想从右侧滑入视图,在横向中它可能会从“顶部”滑入(如果查看其他横向,则可能从底部滑入!)

【讨论】:

  • 喜欢这个问题和答案,但是该死的变换/从错误的方向滑入是一种痛苦
  • 考虑接受我上面的回答?我已经解决了与应用于视图的变换相关的剩余难题,并将解决方案上传到 Github。
  • 也许更好用:toVC.view.frame = transitionContext.finalFrameForViewController(toVC)
【解决方案2】:

我遇到了这个问题,我只是觉得上述解决方案没有任何公正性。我提出了一个不需要 hacky 代码和硬编码帧的解决方案。

UIView 有一个很棒的功能,可以将 CGRect 转换为另一个坐标空间(即+[UIView convertRect:fromView:])。所以我想详细介绍一种更简单的方法,可以在任何方向上实现这种效果,而无需任何硬编码值。在这个例子中,假设我们想要一个从屏幕右侧滑入视图的简单动画。

所以在我们动画师的animateTransition(:) 中,我们可以简单地执行以下操作:

斯威夫特

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    
    let toView = toViewController.view
    let fromView = fromViewController.view
    let containerView = transitionContext.containerView()
    
    if(isPresenting) {
        //now we want to slide in from the right
        let startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0)
        toView.frame = containerView.convertRect(startingRect, fromView:fromView);
        containerView.addSubview(toView)
        let destinationRect = containerView.convertRect(fromView.bounds, fromView: fromView)
        UIView.animateWithDuration(transitionDuration(transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.7,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                toView.frame = destinationRect
        }, completion: { (complete) -> Void in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    } else {
        //we want to slide out to the right
        let endingRect = containerView.convertRect(CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0), fromView: fromView)
        UIView.animateWithDuration(transitionDuration(transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.7,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                fromView.frame = endingRect
            }, completion: { (complete) -> Void in
                if !transitionContext.transitionWasCancelled() {
                    fromView.removeFromSuperview()
                }
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    }
}

目标-C

UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
UIView *toView = toViewController.view;
UIView *fromView = fromViewController.view;
UIView *containerView = [transitionContext containerView];

if(self.isPresenting) {
    //now we want to slide in from the right
    CGRect startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
    toView.frame = [containerView convertRect:startingRect fromView:fromView];
    [containerView addSubview:toView];
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         toView.frame = [containerView convertRect:fromView.bounds
                                                          fromView:fromView];
                     }
                     completion:^(BOOL finished) {
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

} else {
    //we want to slide out to the right
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         CGRect endingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
                         fromView.frame = [containerView convertRect:endingRect fromView:fromView];
                     }
                     completion:^(BOOL finished) {
                         [fromView removeFromSuperview];
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

}

我希望这可以帮助其他在同一条船上来到这里的人(如果确实如此,那么投票不会受到伤害:))

【讨论】:

  • @Vassily 很高兴听到我能帮上忙!
【解决方案3】:

现有的答案是部分但不是全部(我们希望在两个设备上,所有方向,动画和交互式过渡都进行适当的帧和旋转处理)。

这篇博文有帮助:

http://www.brightec.co.uk/blog/ios-7-custom-view-controller-transitions-and-rotation-making-it-all-work

它引用了一位 Apple 支持人员的话说,说明了问题的真实性质:

“对于自定义演示转换,我们在窗口和窗口 rootViewController 的视图之间设置了一个中间视图。这个视图是您在其中执行动画的 containerView。由于 iOS 上的自动旋转实现细节,当界面旋转时我们对窗口 rootViewController 的视图应用仿射变换并相应地修改其边界。因为 containerView 从窗口而不是根视图控制器的视图继承其尺寸,所以它始终处于纵向。"

“如果您的演示动画依赖于演示视图控制器的方向,您将需要检测演示视图控制器的方向并适当地修改您的动画。系统将对传入视图控制器应用正确的变换,但您动画师需要配置传入视图控制器的框架。”

但它不涉及交互式转换。

我在这里找到了一个完整的解决方案:

https://github.com/alfiehanssen/Cards

基本上,您需要根据其中一个 viewController(toViewController 或 fromViewController)的方向而不是 transitionContext 的 containerView 的边界来计算 viewController 的框架。

【讨论】:

  • 在过渡期间将 UIImageView 实例添加到容器视图也会导致不良行为。框架是正确的,但图像的方向与设备的纵向格式对齐。我发现通过使用基于 UINavigationController 的转换,而不是容器视图的框架和方向是正确的。
  • 对于横向模式问题的快速而肮脏的解决方案;应用“代码转换”。即,每当您使用 x 时,使用 y,而不是宽度,使用高度等。
  • 好吧,这实际上就是我上面答案中链接的回购中here 发生的事情。
  • 我的错,现在公开了
  • 请看我关于这个问题的回答,你不需要硬编码你可以使用 convertrect:fromView: 方法的帧值。更清洁、更坚固。
【解决方案4】:

我也被这个问题难住了。我不太喜欢 switch/case 解决方案。我最终创建了这个函数:

@implementation UIView (Extras)

- (CGRect)orientationCorrectedRect:(CGRect)rect {
    CGAffineTransform ct = self.transform;

    if (!CGAffineTransformIsIdentity(ct)) {
        CGRect superFrame = self.superview.frame;
        CGPoint transOrigin = rect.origin;
        transOrigin = CGPointApplyAffineTransform(transOrigin, ct);

        rect.origin = CGPointZero;
        rect = CGRectApplyAffineTransform(rect, ct);

        if (rect.origin.x < 0.0) {
            transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
        }
        if (rect.origin.y < 0.0) {
            transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
        }
        rect.origin = transOrigin;
    }

    return rect;
}

- (CGRect)orientationCorrectedRectInvert:(CGRect)rect {
    CGAffineTransform ct = self.transform;

    if (!CGAffineTransformIsIdentity(ct)) {
        ct = CGAffineTransformInvert(ct);

        CGRect superFrame = self.superview.frame;
        superFrame = CGRectApplyAffineTransform(superFrame, ct);
        CGPoint transOrigin = rect.origin;
        transOrigin = CGPointApplyAffineTransform(transOrigin, ct);

        rect.origin = CGPointZero;
        rect = CGRectApplyAffineTransform(rect, ct);

        if (rect.origin.x < 0.0) {
            transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
        }
        if (rect.origin.y < 0.0) {
            transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
        }

            rect.origin = transOrigin;
        }

        return rect;
    }

基本上,您可以使用纵向或横向坐标创建框架矩形,但在将其应用到视图之前,通过视图转换的函数运行它。使用此方法,您可以使用边界来获得正确的视图大小。

        CGRect endFrame = toViewController.view.frame;
        CGRect startFrame = endFrame;
        startFrame.origin.y = fromViewController.view.bounds.size.height;

        endFrame = [fromViewController.view orientationCorrectedRect:endFrame];
        startFrame = [fromViewController.view orientationCorrectedRect:startFrame];

        toViewController.view.frame = startFrame;

【讨论】:

    【解决方案5】:

    一种解决方案是有一个非常短(或零秒)的过渡,然后一旦过渡完成并呈现您的视图控制器,它将应用正确的变换。然后,您从呈现的视图控制器本身执行动画。

    【讨论】:

    • 注意:这仅在 iOS 7 中相关。在 iOS 8 中,UIViewController 的横向转换按预期工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多