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

Устранение представлений при изменении rootViewController внутри переходаWithView

При исследовании утечки памяти я обнаружил проблему, связанную с методом вызова setRootViewController: внутри блока анимации перехода:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newController; }
                completion:nil];

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

То есть, эта последовательность операций...

  • X становится контроллером Root View
  • X представляет Y, так что Y вид на экране
  • Используя transitionWithView:, чтобы сделать Z новым контроллером Root View

... выглядит хорошо для пользователя, но инструмент "Иерархия просмотра отладки" покажет, что Y-образ все еще находится за Z-представлением, внутри UITransitionView. То есть, после трех шагов выше, иерархия представления:

  • UIWindow
    • UITransitionView
      • UIView (просмотр Y)
    • UIView (просмотр Z)

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

Если я отправлю dismissViewControllerAnimated:NO в X непосредственно перед transitionWithView:, результирующая иерархия представлений:

  • UIWindow
    • UIView (представление X)
    • UIView (просмотр Z)

Если я отправлю dismissViewControllerAnimated: (YES или NO) в X, выполните переход в блоке completion:, тогда иерархия представлений верна. К сожалению, это мешает анимации. Если оживлять увольнение, он тратит время; если он не оживляет, он выглядит сломанным.

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

Конечной целью является переход от представленного представления к новому контроллеру корневого представления напрямую и без оставления иерархии бездействия.

4b9b3361

Ответ 1

У меня недавно была похожая проблема. Мне пришлось вручную удалить этот UITransitionView из окна, чтобы исправить проблему, а затем вызвать dismiss на предыдущем корневом контроллере представления, чтобы убедиться, что он освобожден.

Исправление не очень хорошее, но если вы не нашли лучшего способа с момента публикации вопроса, это единственное, что я нашел для работы! viewController - это просто newController из вашего исходного вопроса.

UIViewController *previousRootViewController = self.window.rootViewController;

self.window.rootViewController = viewController;

// Nasty hack to fix http://stackoverflow.com/questions/26763020/leaking-views-when-changing-rootviewcontroller-inside-transitionwithview
// The presenting view controllers view does not get removed from the window as its currently transistioning and presenting a view controller
for (UIView *subview in self.window.subviews) {
    if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
        [subview removeFromSuperview];
    }
}
// Allow the view controller to be deallocated
[previousRootViewController dismissViewControllerAnimated:NO completion:^{
    // Remove the root view in case its still showing
    [previousRootViewController.view removeFromSuperview];
}];

Надеюсь, это тоже поможет тебе решить проблему, это абсолютная боль в заднице!

Swift 3.0

(Смотрите историю изменений для других версий Swift)

Для более приятной реализации в качестве расширения в UIWindow, позволяющего передавать необязательный переход.

extension UIWindow {

    /// Fix for http://stackoverflow.com/a/27153956/849645
    func set(rootViewController newRootViewController: UIViewController, withTransition transition: CATransition? = nil) {

        let previousViewController = rootViewController

        if let transition = transition {
            // Add the transition
            layer.add(transition, forKey: kCATransition)
        }

        rootViewController = newRootViewController

        // Update status bar appearance using the new view controllers appearance - animate if needed
        if UIView.areAnimationsEnabled {
            UIView.animate(withDuration: CATransaction.animationDuration()) {
                newRootViewController.setNeedsStatusBarAppearanceUpdate()
            }
        } else {
            newRootViewController.setNeedsStatusBarAppearanceUpdate()
        }

        if #available(iOS 13.0, *) {
            // In iOS 13 we don't want to remove the transition view as it'll create a blank screen
        } else {
            // The presenting view controllers view does not get removed from the window as its currently transistioning and presenting a view controller
            if let transitionViewClass = NSClassFromString("UITransitionView") {
                for subview in subviews where subview.isKind(of: transitionViewClass) {
                    subview.removeFromSuperview()
                }
            }
        }
        if let previousViewController = previousViewController {
            // Allow the view controller to be deallocated
            previousViewController.dismiss(animated: false) {
                // Remove the root view in case its still showing
                previousViewController.view.removeFromSuperview()
            }
        }
    }
}

Использование:

window.set(rootViewController: viewController)

Или

let transition = CATransition()
transition.type = kCATransitionFade
window.set(rootViewController: viewController, withTransition: transition)

Ответ 2

Я столкнулся с этой проблемой, и это раздражало меня целый день. Я пробовал решение @Rich obj-c и выясняется, что после этого я хочу представить другой viewController, я буду заблокирован пустым UITransitionView.

Наконец, я понял это, и это сработало для меня.

- (void)setRootViewController:(UIViewController *)rootViewController {
    // dismiss presented view controllers before switch rootViewController to avoid messed up view hierarchy, or even crash
    UIViewController *presentedViewController = [self findPresentedViewControllerStartingFrom:self.window.rootViewController];
    [self dismissPresentedViewController:presentedViewController completionBlock:^{
        [self.window setRootViewController:rootViewController];
    }];
}

- (void)dismissPresentedViewController:(UIViewController *)vc completionBlock:(void(^)())completionBlock {
    // if vc is presented by other view controller, dismiss it.
    if ([vc presentingViewController]) {
        __block UIViewController* nextVC = vc.presentingViewController;
        [vc dismissViewControllerAnimated:NO completion:^ {
            // if the view controller which is presenting vc is also presented by other view controller, dismiss it
            if ([nextVC presentingViewController]) {
                [self dismissPresentedViewController:nextVC completionBlock:completionBlock];
            } else {
                if (completionBlock != nil) {
                    completionBlock();
                }
            }
        }];
    } else {
        if (completionBlock != nil) {
            completionBlock();
        }
    }
}

+ (UIViewController *)findPresentedViewControllerStartingFrom:(UIViewController *)start {
    if ([start isKindOfClass:[UINavigationController class]]) {
        return [self findPresentedViewControllerStartingFrom:[(UINavigationController *)start topViewController]];
    }

    if ([start isKindOfClass:[UITabBarController class]]) {
        return [self findPresentedViewControllerStartingFrom:[(UITabBarController *)start selectedViewController]];
    }

    if (start.presentedViewController == nil || start.presentedViewController.isBeingDismissed) {
        return start;
    }

    return [self findPresentedViewControllerStartingFrom:start.presentedViewController];
}

Хорошо, теперь вам нужно только позвонить [self setRootViewController:newViewController];, если вы хотите переключить контроллер корневого представления.

Ответ 3

Я пробую простую вещь, которая работает для меня на iOS 9.3: просто удалите старый viewController view из своей иерархии во время завершения dismissViewControllerAnimated.

Позвольте работать над представлениями X, Y и Z, как описано в benzado:

То есть, эта последовательность операций...

  • X становится контроллером Root View
  • X представляет Y, так что Y вид на экране
  • Использование переходаWithView: сделать Z новым Root View Controller

Что дает:

////
//Start point :

let X = UIViewController ()
let Y = UIViewController ()
let Z = UIViewController ()

window.rootViewController = X
X.presentViewController (Y, animated:true, completion: nil)

////
//Transition :

UIView.transitionWithView(window,
                          duration: 0.25,
                          options: UIViewAnimationOptions.TransitionFlipFromRight,
                          animations: { () -> Void in
                                X.dismissViewControllerAnimated(false, completion: {
                                        X.view.removeFromSuperview()
                                    })
                                window.rootViewController = Z
                           },
                           completion: nil)

В моем случае X и Y являются хорошо dealloc, и их представление больше не в иерархии!

Ответ 4

Я пришел к этой проблеме при использовании этого кода:

if var tc = self.transitionCoordinator() {

    var animation = tc.animateAlongsideTransitionInView((self.navigationController as VDLNavigationController).filtersVCContainerView, animation: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in
        var toVC = tc.viewControllerForKey(UITransitionContextToViewControllerKey) as BaseViewController
        (self.navigationController as VDLNavigationController).setFilterBarHiddenWithInteractivity(!toVC.filterable(), animated: true, interactive: true)
    }, completion: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in

    })
}

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

Это не тот ответ, который вы ищете, но он может привести вас к правильной панели для поиска вашего решения.