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

Перемещение rootViewController с анимацией

У меня небольшая проблема с заменой rootViewControllers анимацией. Вот код, который я использую:

[UIView transitionWithView:self.window duration:0.8 options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
        self.window.rootViewController = self.navigationController;
    } completion:nil];

Это работает, за исключением того, что прямо перед анимацией экран становится черным, а затем происходит анимация. Похоже, что первоначальный rootViewController удаляется прямо перед анимацией. Любые идеи?

4b9b3361

Ответ 1

transitionWithView предназначен для анимации подсмотров указанного вида контейнера. Не так просто обновить контроллер корневого представления. Я долгое время пытался сделать это без побочных эффектов. См:

Анимировать изменения контроллеров представления без использования стека контроллера навигации, подсмотров или модальных контроллеров?

EDIT: добавлена ​​выдержка из указанного ответа

[UIView transitionFromView:self.window.rootViewController.view
                    toView:viewController.view
                  duration:0.65f
                   options:transition
                completion:^(BOOL finished){
                    self.window.rootViewController = viewController;
                }];

Ответ 2

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

Я исправил это, сняв последний экран первого контроллера представления, наложив его поверх второго (целевого) контроллера представления, а затем анимировал его, как я хотел после. Я установил второй контроллер представления как корень:

UIView *overlayView = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
[self.destinationViewController.view addSubview:overlayView];
self.window.rootViewController = self.destinationViewController;

[UIView animateWithDuration:0.4f delay:0.0f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
    overlayView.alpha = 0;
} completion:^(BOOL finished) {
    [overlayView removeFromSuperview];
}];

EDIT: версия версии Swift 3:

let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false)
destinationViewController.view.addSubview(overlayView)
window.rootViewController = destinationViewController

UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve, animations: {
    overlayView.alpha = 0
}, completion: { finished in
    overlayView.removeFromSuperview()
})

Ответ 3

Я нашел переходWithView: duration: options: animation: завершение:, чтобы получить более надежный результат.

[UIView transitionWithView:window
                  duration:0.3
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
                    [fromView removeFromSuperview];
                    [window addSubview:toView];
                    window.rootViewController = toViewController;
                }
                completion:NULL];

Если вы оставите его до блока завершения, чтобы установить контроллер корневого представления, тогда во время таких методов, как view (Will/Did) Appear

self.view.window.rootViewController

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

Ответ 4

Решение, которое сработало для меня, было небольшой модификацией ответа Марко Николовского. У моего существующего контроллера корневого представления был анимированный прядильщик, поэтому снимок выглядел странно, потому что он заморозил анимацию. В конце концов, я смог сделать следующее (внутри текущего контроллера корневого представления):

NextRootViewController *nextRootVC = [self.storyboard instantiateViewControllerWithIdentifier:@"NextRootViewController"];

self.view.window.rootViewController = nextRootVC;

[nextRootVC addSubview:self.view];

[UIView animateWithDuration:0.4 delay:0.2 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
    self.view.alpha = 0;
} completion:^(BOOL finished) {
    [self.view removeFromSuperview];
}];

Ответ 5

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

Взяв cclogg basic idea, здесь моя реализация для плавного перехода между двумя контроллерами представлений, которые не беспокоят альфа-значения:

/// Replaces the window current `rootViewController` with the `newViewController` 
/// passed as a parameter. If `animated`, the window animates the replacement 
/// with a cross dissolve transition.
///
/// - Parameters:
///   - newViewController: The view controller that replaces the current view controller.
///   - animated: Specifies if the transition between the two view controllers is animated.
///   - duration: The transition duration. (Ignored if `animated == false`)
private func swapCurrentViewController(with newViewController: UIViewController, 
                                       animated: Bool = true, 
                                       duration: TimeInterval = 1) {

    // Get a reference to the window current `rootViewController` (the "old" one)
    let oldViewController = window?.rootViewController

    // Replace the window `rootViewController` with the new one
    window?.rootViewController = newViewController

    if animated, let oldView = oldViewController?.view {

        // Add the old view controller view on top of the new `rootViewController`
        newViewController.view.addSubview(oldView)

        // Remove the old view controller view in an animated fashion
        UIView.transition(with: window!,
                          duration: duration,
                          options: .transitionCrossDissolve,
                          animations: { oldView.removeFromSuperview() },
                          completion: nil)
    }
}

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


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

Ответ 6

Вместо того, чтобы обменивать rootViewControllers, почему бы вам не создать пользовательский вид или, может быть, просто UINavigationController (не показывать заголовок) и поместить "rootViews", который вы хотите поменять под ним.