NSNotification: попытка представить UIAlertController на ViewController, чье представление не находится в иерархии окон.

Я пытаюсь показать UIAlertController в моем ViewController в функции, которая была вызвана через NSNotification. Однако я получаю сообщение об ошибке:

Attempt to present <UIAlertController: 0x7fe013d05d40> on <submarine.ViewController: 0x7fe011f20370> whose view is not in the window hierarchy!

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

Мой код:

override func viewDidAppear(animated: Bool) {
    // Handle onboarding
    if needsOnboarding() {
        handleOnboarding() // This create the completion block that posts the NSNotification
    }

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "showTermsAlert:", name:"showTermsAlert", object: nil)
}

func showTermsAlert(notification: NSNotification) {
    let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)

    termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
        UIApplication.sharedApplication().openURL(NSURL(string: "my_terms_url")!)
    }))

    termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
        self.onboardingFinished()
    }))

    self.presentViewController(termsAlert, animated: true, completion: nil)
}

Кто-нибудь понял, почему это происходит? Я не понимаю, почему его нет в иерархии окон - он представлен из self viewController и создается в функции верхнего уровня внутри VC.

Спасибо!

РЕДАКТИРОВАТЬ: исходный код внутри handleOnboarding():

Используемая библиотека: Onboard

func handleOnboarding() {

        let secondPage = OnboardingContentViewController(title: "What's going on?", body: "Submarine routes your data through our network, around any filters and restrictions, giving you unrestricted and unmonitored internet access.", image: UIImage(named: "back"), buttonText: "Next") { () -> Void in
            // do something here when users press the button, like ask for location services permissions, register for push notifications, connect to social media, or finish the onboarding process
        }
        secondPage.movesToNextViewController = true

        let thirdPage = OnboardingContentViewController(title: "Terms of Use", body: "You must agree to our Terms of Use to use Submarine.\nIf you don't, please close Submarine.", image: UIImage(named: "back"), buttonText: "View Terms") { () -> Void in

            let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)

            termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
                UIApplication.sharedApplication().openURL(NSURL(string: "my_policy_url")!)
            }))

            termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
                self.onboardingFinished()
            }))

            self.presentViewController(termsAlert, animated: true, completion: nil)

//            NSNotificationCenter.defaultCenter().postNotificationName("showTermsAlert", object: nil)
        }

        // Image
        let onboardingVC = OnboardingViewController(backgroundImage: UIImage(named: "back"), contents: [secondPage, thirdPage])

        self.navigationController?.presentViewController(onboardingVC, animated: false, completion: nil)

    }

person Sam Heather    schedule 19.03.2016    source источник


Ответы (2)


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

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

person Leo Natan    schedule 19.03.2016
comment
Обычно метод viewWillDisappear: является хорошим местом для удаления наблюдателей. - person Sulthan; 19.03.2016
comment
@Sulthan Может быть, в этом случае, а может и нет. Я специально не хотел вносить конкретное предложение, так как у нас недостаточно информации. Кроме того, обычно логика viewDidAppear:/viewWillDisappear: глючит и часто проблематична, как мы видим здесь. - person Leo Natan; 19.03.2016
comment
@Sulthan, поэтому в handleOboarding () я нажимаю новый VC на NavigationController, а затем в обратном вызове, который происходит после действия пользователя в этом контроллере, я пытаюсь показать предупреждение, вызвав showTermsAlert(). Это немного проясняет ситуацию? - person Sam Heather; 19.03.2016

Есть несколько вещей, которые я бы изменил в вашем коде. Добавьте вызов super в viewDidAppear: и перестаньте использовать NSNotification в своей презентации. Вы не знаете, какой поток showTermsAlert будет вызван с помощью этого шаблона. Вы можете сделать свое намерение более явным, вызвав showTermsAlert напрямую, и это также гарантирует, что вы находитесь в основном потоке.

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    // Handle onboarding
    if needsOnboarding() {
        self.showTermsAlert()
    }
}

func showTermsAlert() {
    let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)

    termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
    UIApplication.sharedApplication().openURL(NSURL(string: "my_terms_url")!)
}))

    termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
    self.onboardingFinished()
}))

    self.presentViewController(termsAlert, animated: true, completion: nil)
}
person Nick    schedule 19.03.2016
comment
Я не могу поместить его в блок if для needsOnboarding() { }, потому что строка, которая была там (handleOnboarding()), создает новый VC, помещает его в NavigationController, а затем делает что-то, что создает обратный вызов, когда он завершен. Именно в этом обратном вызове (после того, как эта штука закончилась) мне нужно вызвать showTermsAlert(). Это немного проясняет ситуацию? - person Sam Heather; 19.03.2016
comment
да я так думаю. исходя из этого, я думаю, что showTermsAlert() мог быть вызван слишком рано, до того, как новый VC был полностью удален из экрана и иерархии окон? Также убедитесь, что вы вызываете self.PresentViewController в основном потоке. - person Nick; 19.03.2016
comment
Только что опубликовал обновление, включающее код для handleOnboarding(), как я пытался это сделать до NSNotification. Сейчас я быстро посмотрю на основную тему :) - person Sam Heather; 19.03.2016