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