Подтвердить что ты не робот

Как использовать автоматический макет с контейнерными переходами?

Как вы можете использовать автоматический макет с помощью метода перехода контейнера UIViewController:

-(void)transitionFromViewController:(UIViewController *)fromViewController
                   toViewController:(UIViewController *)toViewController 
                           duration:(NSTimeInterval)duration
                            options:(UIViewAnimationOptions)options
                         animations:(void (^)(void))animations
                         completion:(void (^)(BOOL finished))completion;

Традиционно, используя Springs/Struts, вы устанавливаете начальные кадры (непосредственно перед вызовом этого метода) и настраиваете финальные кадры в блоке анимации, которые вы передаете методу.

Этот метод выполняет работу по добавлению представления в иерархию представлений и выполнению анимаций для вас.

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

Любые идеи, как я могу использовать этот метод вместе с Auto Layout?

Ниже приведен пример (Спасибо, кокоэнетика) за это, используя Springs/Struts (frames) http://www.cocoanetics.com/2012/04/containing-viewcontrollers

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{

    // XXX We can't add constraints here because the view is not yet in the view hierarchy
    // animation setup 
    toViewController.view.frame = _containerView.bounds;
    toViewController.view.autoresizingMask = _containerView.autoresizingMask;

    // notify
    [fromViewController willMoveToParentViewController:nil];
    [self addChildViewController:toViewController];

    // transition
    [self transitionFromViewController:fromViewController
                      toViewController:toViewController
                              duration:1.0
                               options:UIViewAnimationOptionTransitionCurlDown
                            animations:^{
                            }
                            completion:^(BOOL finished) {
                                [toViewController didMoveToParentViewController:self];
                                [fromViewController removeFromParentViewController];
                            }];
}
4b9b3361

Ответ 1

Начинаем думать об утилите transitionFromViewController: toViewController: duration: options: animations: завершение не может быть выполнено, чтобы работать с Auto Layout.

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

Он выглядит следующим образом:

- (void) performTransitionFromViewController:(UIViewController*)fromVc toViewController:(UIViewController*)toVc {

    [fromVc willMoveToParentViewController:nil];
    [self addChildViewController:toVc];

    UIView *toView = toVc.view;
    UIView *fromView = fromVc.view;

    [self.containerView addSubview:toView];

    // TODO: set initial layout constraints here

    [self.containerView layoutIfNeeded];

    [UIView animateWithDuration:.25
                          delay:0
                        options:0
                     animations:^{

                         // TODO: set final layout constraints here

                         [self.containerView layoutIfNeeded];

                     } completion:^(BOOL finished) {
                         [toVc didMoveToParentViewController:self];
                         [fromView removeFromSuperview];
                         [fromVc removeFromParentViewController];
                     }];
}

Ответ 2

Реальное решение, похоже, заключается в настройке ваших ограничений в блоке анимации transitionFromViewController:toViewController:duration:options:animations:.

[self transitionFromViewController:fromViewController
                  toViewController:toViewController
                          duration:1.0
                           options:UIViewAnimationOptionTransitionCurlDown
                        animations:^{
                            // SET UP CONSTRAINTS HERE
                        }
                        completion:^(BOOL finished) {
                            [toViewController didMoveToParentViewController:self];
                            [fromViewController removeFromParentViewController];
                        }];

Ответ 3

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

TL; версия DR

Если вам нужно только позиционировать представление с помощью автоматического макета, вы можете использовать метод -[UIViewController transitionFromViewController:toViewController:duration:options:animations:completion:] и установить ограничения в блоке анимации.

Если вам нужно анимировать изменения ограничений автоматического макета, вы должны использовать общий вызов +[UIView animateWithDuration:delay:options:animations:completion:] и регулярно добавлять дочерний контроллер.

Решение 1: Поместите вид через автоматический макет

Сначала разрешите первый, простой случай. В этом случае представление должно быть расположено с помощью автоматического макета, так что изменения высоты строки состояния (например, путем выбора Переключить строку состояния вызова), помимо прочего, не будут вытеснять вещи с экрана.

Для справки: официальный код Apple относительно перехода от одного контроллера к другому:

- (void) cycleFromViewController: (UIViewController*) oldC
            toViewController: (UIViewController*) newC
{
    [oldC willMoveToParentViewController:nil];                        // 1
    [self addChildViewController:newC];

    newC.view.frame = [self newViewStartFrame];                       // 2
    CGRect endFrame = [self oldViewEndFrame];

    [self transitionFromViewController: oldC toViewController: newC   // 3
          duration: 0.25 options:0
          animations:^{
             newC.view.frame = oldC.view.frame;                       // 4
             oldC.view.frame = endFrame;
           }
           completion:^(BOOL finished) {
             [oldC removeFromParentViewController];                   // 5
             [newC didMoveToParentViewController:self];
            }];
}

Вместо использования фреймов, как в приведенном выше примере, мы должны добавить ограничения. Вопрос в том, где их добавить. Мы не можем добавить их в маркер (2) выше, поскольку newC.view не установлен в иерархии представлений. Он устанавливается только в тот момент, когда мы вызываем transitionFromViewController... (3). Это означает, что мы можем либо установить ограничения сразу после вызова переходаFromViewController, либо мы можем сделать это как первую строку в блоке анимации. Оба должны работать. Если вы хотите сделать это в кратчайшие сроки, то положить его в блок анимации - это путь. Более подробно о порядке вызова этих блоков будет рассказано ниже.

В целом, для простого позиционирования с помощью автоматического макета используйте шаблон, например:

- (void)cycleFromViewController:(UIViewController *)oldViewController
               toViewController:(UIViewController *)newViewController
{
    [oldViewController willMoveToParentViewController:nil];
    [self addChildViewController:newViewController];

    newViewController.view.alpha = 0;

    [self transitionFromViewController:oldViewController
                      toViewController:newViewController
                              duration:0.25
                               options:0
                            animations:^{
                                newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
                                // create constraints for newViewController.view here

                                newViewController.view.alpha = 1;
                            }
                            completion:^(BOOL finished) {
                                [oldViewController removeFromParentViewController];
                                [newViewController didMoveToParentViewController:self];
                            }];
    // or create constraints right here
}

Решение 2: Анимация изменений ограничений

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

Для справки, здесь является стандартным способом добавления/удаления контроллера детского представления:

- (void) displayContentController: (UIViewController*) content;
{
   [self addChildViewController:content];                 // 1
   content.view.frame = [self frameForContentController]; // 2
   [self.view addSubview:self.currentClientView];
   [content didMoveToParentViewController:self];          // 3
}

- (void) hideContentController: (UIViewController*) content
{
   [content willMoveToParentViewController:nil];  // 1
   [content.view removeFromSuperview];            // 2
   [content removeFromParentViewController];      // 3
}

Сравнивая эти два метода и оригинальный циклFromViewController: выше, мы видим, что переходFromViewController выполняет две вещи для нас:

  • [self.view addSubview:self.currentClientView];
  • [content.view removeFromSuperview];

Добавив некоторые записи (опущенные из этого сообщения), мы можем получить представление о том, когда вызывается эти методы.

После этого оказывается, что метод реализован так, как показано ниже:

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
{
    [self.view addSubview:toViewController.view];  // A
    animations();                                  // B

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [fromViewController.view removeFromSuperview];
        completion(YES);
    });
}

Теперь ясно, почему невозможно использовать переход для перехода к изменениям ограничений. В первый раз вы можете инициализировать ограничения после добавления вида (строка A). Ограничения должны быть анимированы в блоке animations() (строка B), но не существует способа запуска кода между этими двумя строками.

Поэтому мы должны использовать блок ручной анимации вместе со стандартным методом анимации ограничений :

- (void)cycleFromViewController:(UIViewController *)oldViewController
               toViewController:(UIViewController *)newViewController
{
    [oldViewController willMoveToParentViewController:nil];
    [self addChildViewController:newViewController];

    [self.view addSubview:newViewController.view];
    newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
    // TODO: create initial constraints for newViewController.view here

    [newViewController.view layoutIfNeeded];

    // TODO: update constraint constants here

    [UIView animateWithDuration:0.25
                     animations:^{
                         [newViewController.view layoutIfNeeded];
                     }
                     completion:^(BOOL finished) {
                         [oldViewController.view removeFromSuperview];
                         [oldViewController removeFromParentViewController];
                         [newViewController didMoveToParentViewController:self];
                     }];
}

Предупреждения

Это не эквивалентно тому, как раскадровка встраивает контроллер представления контейнера. Например, если вы сравниваете значение translatesAutoresizingMaskIntoConstraints встроенного представления через раскадровку и метод выше, он будет сообщать YES для раскадровки и NO (очевидно, так как мы явно устанавливаем его в NO) для метод, который я рекомендую выше.

Это может привести к несоответствиям в вашем приложении, так как некоторые части системы, похоже, зависят от UIViewController, которые будут использоваться с translatesAutoresizingMaskIntoConstraints, установленными на NO. Например, на iPad Air (8.4), вы можете получить странное поведение при повороте с портрета на пейзаж.

Простым решением является сохранение translatesAutoresizingMaskIntoConstraints в NO, затем установка newViewController.view.frame = newViewController.view.superview.bounds. Однако, если вы не очень осторожны при вызове этого метода, скорее всего, это даст вам неправильный визуальный макет. (Примечание: способ, которым раскадровка обеспечивает правильность размеров представления, заключается в установке свойства встроенного вида autoresize на W+H. Распечатка кадра сразу после добавления поднабора также выявит разницу между раскадровкой и программным подходом, который предполагает, что Apple устанавливает фрейм непосредственно на скрытый вид.)

Ответ 4

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

Здесь вывод, который я изложил из моих опытов: вы не можете использовать автоматическую компоновку непосредственно в корневом представлении контроллера вида. Как только я установил translatesAutoresizingMaskIntoConstraints в NO в корневом представлении, я начинаю получать ошибки или хуже.

Поэтому вместо этого я использую гибридное решение. Я устанавливаю фреймы и использую авторезистировку для размещения и размера корневого представления в макете, который в противном случае настраивается с помощью автоматического макета. Например, здесь, как я загружаю контроллер представления страниц в качестве контроллера детского представления в viewDidLoad в приложении, использующем автоматический макет:

self.pageViewController = ...  
...

[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];

// could not get constraints to work here (using autoresizing mask)
self.pageViewController.view.frame = self.view.bounds;

[self.pageViewController didMoveToParentViewController:self];

Так Apple загружает контроллер дочернего представления в шаблон Xcode "Page на основе приложения", и это выполняется в проекте с поддержкой автоматического макета.

Итак, если бы я был вами, я бы попробовал установить кадры для анимации перехода контроллера представления и посмотреть, что произойдет. Дайте мне знать, как это работает.