Обходной путь для пользовательских анимаций UIViewController в ландшафте?

У меня есть настраиваемый анимированный переход 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 не является надежным, когда представление имеет преобразование, поэтому я предполагаю, что это корень проблемы. В ландшафтном режиме просмотр/просмотр viewControllers имеет 90 градусов по часовой стрелке [0 -1 1 0]. Я пробовал использовать границы/центр для определения размера и расположения представления, а также удаления преобразования, а затем повторного применения его, но UIKit борется со мной и настаивает на отображении вида в виде портрета. Досадно!

На скриншоте темно-серый - это фон UIWindow, а красный - добавленный контроллер модального представления, который должен охватывать весь экран.

the red view is in portrait

Кто-нибудь нашел обходное решение?

Ответы

Ответ 1

Хорошо, исправление на удивление просто:

Установите рамку toViewController в контейнер до, добавив представление в контейнер.

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

Обновление:. По-прежнему существует ограничение в том, что вы не знаете ориентацию фрейма. Сначала это портрет, но растянутый в пейзаж, когда он отображается на экране. Если вы хотите скользить по экрану справа, в ландшафте, он может скользить из "верхней" (или снизу, если смотреть на другой пейзаж!)

Ответ 2

Я столкнулся с этой проблемой, и я просто не чувствую, что вышеупомянутые решения делают это любой справедливостью. Я предлагаю решение, которое не требует взломанного кода и жестко закодированных кадров.

UIView имеет потрясающую функцию для преобразования CGRect в координатное пространство другого (а именно: +[UIView convertRect:fromView:]). Поэтому я хочу рассказать о гораздо более простом способе достижения этого эффекта в любой ориентации без каких-либо жестко заданных значений. В этом примере можно сказать, что мы хотим, чтобы простая анимация сместила представление справа от экрана.

Итак, в нашем аниматоре animateTransition(:) мы могли бы просто выполнить следующее:

Swift

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())
        })
    }
}

Objective-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]];
                     }];

}

Я надеюсь, что это поможет кому-то еще, кто пришел сюда в одной лодке (если это произойдет, голосование не повредит:))

Ответ 3

Существующий ответ идет частично, но не полностью (нам нужны правильные кадры и обработка вращения на обоих устройствах, все ориентации, как для анимированных, так и для интерактивных переходов).

Это сообщение в блоге помогает:

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

И он цитирует сотрудника Apple Support, в котором изложена истинная природа проблемы:

"Для пользовательских презентационных переходов мы устанавливаем промежуточный вид между окном и представлением rootViewController. Это представление представляет собой контейнер, в котором вы выполняете анимацию. Из-за детализации реализации автоматического поворота на iOS, когда интерфейс вращается мы применяем аффинное преобразование к представлению rootViewController Windows и соответствующим образом изменяем его границы. Поскольку containerView наследует свои размеры из окна вместо представления контроллера корневого представления, он всегда находится в портретной ориентации."

"Если ваша анимация презентации зависит от ориентации контроллера представления, вам нужно будет определить ориентацию представления представления и соответствующим образом изменить свою анимацию. Система применит правильное преобразование к контроллеру входящего представления, но вы аниматору необходимо настроить кадр входящего контроллера вида.

Но он не затрагивает интерактивные переходы.

Я разработал полное решение проблемы здесь:

https://github.com/alfiehanssen/Cards

По сути, вам нужно вычислить фреймы ваших контрольных объектов на основе ориентации одного из viewControllers (toViewController или fromViewController), а не границ переходного контейнера контейнера.

Ответ 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

Одним из решений является очень короткий (или нулевой) переход, а затем, как только переход закончен, и будет представлен ваш контроллер представления, он будет иметь правильные преобразования, применяемые к нему. Затем вы выполняете анимацию внутри самого отображаемого контроллера.