Crash on UITextView phone number selection

Seems that iOS 9 has some bug with presenting alert after UITextView phone number selection. This occurs only if this UITextView is on the view controllers which has been modally presented over root view controller.

Log:

Warning: Attempt to present <_UIRotatingAlertController: 0x1866aa00> on <HomeViewController: 0x17ad8e00> whose view is not in the window hierarchy!

*** Assertion failure in -[UITextView startInteractionWithLinkAtPoint:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3505.16/UITextView_LinkInteraction.m:377

Replies

I saw this for "event" links (e.g. "4pm") from UITextView with dataDetectorTypes set to UIDataDetectorTypeAll. The work around I used was to override -[presentViewController:animated:completion:] in my root view controller. Here's the gist of it:


- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
    if ([viewControllerToPresent isKindOfClass:UIAlertController.class] && !self.view.window) {
        UIViewController *vc = <code to find the top most view controller>;
        [vc presentViewController:viewControllerToPresent animated:flag completion:completion];
    } else {
        [super presentViewController:viewControllerToPresent animated:flag completion:completion];
    }
}

I tested today with iOS 9.2.1 and I was still seeing the issue where the root view controller was being used to present the UIAlertController, but my tweak above gets around the problem.

I am not proud of this hack, but I didn't want to lose all of the functionality provided by default for phone numbers. As a result, I was able to get things to work via the following:


If you are getting an error in your log that says "Attempt to present ... on ... whose view is not in the window hierarchy", it means that your `window.rootViewController.view` is not in the window's hierarchy. This is likely the case because you have presented something full screen modal on top of it. In order to get past this, find all of the full screen modals in the view controller hierarchy up to your screen with your `UITextView` and change the `modalPresentationStyle` on the presented view controller to `.OverFullScreen`.


Hopefully, this gets you past the first issue, but then, when you try again, you will get a new error in your log that says "Attempt to present ... on ... which is already presenting".


In order to get past this one, I followed nate.m's lead and overroad the `presentViewController` method in my rootViewController class. (Mine is custom, but if you have something like a `UINavigationController` here, I imagine that you are going to have to subclass it.) Then, in this overridden implementation, walk through the `presentedViewControllers` until you reach one that is `nil`. When you do, present from that view controller. My implementation looks like this:


    override func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
        guard let alertController = viewControllerToPresent as? UIAlertController else {
            super.presentViewController(viewControllerToPresent, animated: flag, completion: completion)
            return
        }
        var presentingVC: UIViewController = self
        while let presentedVC = presentingVC.presentedViewController {
            presentingVC = presentedVC
        }
        presentingVC.presentViewController(alertController, animated: flag, completion: completion)
    }


Again, I am not proud of this, but it was the only thing that I could figure out in order to get system behavior for the phone number links.


I would really love to know what UIKit is doing in this instance such that it is trying to present from `window.rootViewController`?


Let me know if this works for you, or if you find something better.


Hope this helps.

In my case a simple clean did the job.

We've recently fixed this after stumbling on this issue in our PSPDFKit.


The fix dynamically applies and removes a workaround. This is better than brutally overriding presentViewController as this might swallow valid bugs.


https://gist.github.com/steipete/b00fc02aa9f1c66c11d0f996b1ba1265


We also reported this as rdar://26295020 to Apple. Please dupe!

Thank you so much! I googled this issue for days. I directly copy and pasted your code into my root view controller and all issues were instantly resolved. Bravo!