Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iPhone 6 Plus UISplitViewController crash with recursive _canBecomeDeepestUnambiguousResponder

I have an existing iPhone app that I'm adding a UISplitViewController to. The iPad part works like a charm, but I'm having a guaranteed crash with the iPhone 6(S) Plus.

Setup - Master is a UITabBarController. Initial detail is a view with a placeholder logo view. Once an object is selected, detail is replaced with a UITabBarController.

Whenever I select an item and open up Detail in the iPhone 6 Plus and rotate it from portrait (detail only visible) to landscape (where the master would be visible), it crashes. This does not occur on rotation with the placeholder detail view.

Before the crash, it does call the delegate methods primaryViewControllerForExpandingSplitViewController and splitViewController(splitViewController: UISplitViewController, separateSecondaryViewControllerFromPrimaryViewController. However, everything works fine on the iPad.

I've done a ton of searching already and only seen a couple twitter mentions of this type of crash. Things like setting or not setting the displayModeButtonItem don't help.

I recreated this crash in a fresh project - it can be downloaded here: https://github.com/sschale/SplitViewCrash/

Crash log:

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_PROTECTION_FAILURE at 0x00007fff53609ff8
Exception Note:        EXC_CORPSE_NOTIFY

VM Regions Near 0x7fff53609ff8:
    MALLOC_TINY            00007f8405000000-00007f8405300000 [ 3072K] rw-/rwx SM=PRV  
--> STACK GUARD            00007fff4fe0a000-00007fff5360a000 [ 56.0M] ---/rwx SM=NUL  stack guard for thread 0
    Stack                  00007fff5360a000-00007fff53dff000 [ 8148K] rw-/rwx SM=COW  thread 0

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0   liboainject.dylib              0x000000010e5e59b2
0   liboainject.dylib               0x000000010e5e59b2 

_writeEventToSharedMemory + 27
1   liboainject.dylib               0x000000010e5e55d7 _OARecordFinalEvent + 1161
2   liboainject.dylib               0x000000010e5e79f1 ___swapMethods_block_invoke_6 + 338
3   libobjc.A.dylib                 0x000000010f4f9b6b weak_read_no_lock + 89
4   libobjc.A.dylib                 0x000000010f4fa4c6 objc_loadWeakRetained + 104
5   com.apple.UIKit                 0x00000001110510b6 -[UIViewController presentedViewController] + 58
6   com.apple.UIKit                 0x0000000111033fc6 -[UIViewController _canBecomeDeepestUnambiguousResponder] + 31
7   com.apple.UIKit                 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
8   com.apple.UIKit                 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
9   com.apple.UIKit                 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
10  com.apple.UIKit                 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
//(500 more of those)
....

Thread 1:: Dispatch queue: com.apple.libdispatch-manager
0   libsystem_kernel.dylib          0x0000000116e49ee2 kevent64 + 10
1   libdispatch.dylib               0x0000000116ac57f0 _dispatch_mgr_invoke + 260
2   libdispatch.dylib               0x0000000116ac558a _dispatch_mgr_thread + 54

Thread 2:
0   libsystem_kernel.dylib          0x0000000116e495e2 __workq_kernreturn + 10
1   libsystem_pthread.dylib         0x0000000116e0d578 _pthread_wqthread + 1283
2   libsystem_pthread.dylib         0x0000000116e0b341 start_wqthread + 13

Thread 3:
0   libsystem_kernel.dylib          0x0000000116e495e2 __workq_kernreturn + 10
1   libsystem_pthread.dylib         0x0000000116e0d578 _pthread_wqthread + 1283
2   libsystem_pthread.dylib         0x0000000116e0b341 start_wqthread + 13

Thread 4:
0   libsystem_kernel.dylib          0x0000000116e495e2 __workq_kernreturn + 10
1   libsystem_pthread.dylib         0x0000000116e0d578 _pthread_wqthread + 1283
2   libsystem_pthread.dylib         0x0000000116e0b341 start_wqthread + 13

Thread 5:
0   libsystem_kernel.dylib          0x0000000116e495e2 __workq_kernreturn + 10
1   libsystem_pthread.dylib         0x0000000116e0d578 _pthread_wqthread + 1283
2   libsystem_pthread.dylib         0x0000000116e0b341 start_wqthread + 13
like image 304
sschale Avatar asked Jun 01 '16 20:06

sschale


1 Answers

This crashes on iPad too. Use Multitasking to resize the app to Compact width (e.g. 1/3rd screen), press the Launch Detail button, then resize it to Regular width.

When you're in Compact width, the split view controller is "collapsed". That means that it no longer shows separate primary and secondary view controllers at the same time -- instead, it "collapses" them into a single view controller hierarchy. When it's in that environment, it often needs your help in order to act sensibly. The default behavior works well when both your primary and secondary view controllers are UINavigationControllers, but not in other cases.

(In your app, your primary is a UITabBarController, and after you "Launch Detail" once, the secondary is also a UITabBarController. You may want to reconsider that design, because it makes things more difficult. Keep reading.)

Your app's "Launch Detail" button performs a "Show Detail" segue, which effectively calls this method on UISplitViewController:

public func showDetailViewController(vc: UIViewController, sender: AnyObject?)

Note the comment in the header:

// In a horizontally-compact environment the master view controller
// or detail view controller is sent the showViewController:sender:
// message. If neither one of them provide an implementation for this
// method then it will fall back to a full screen presentation.

By "master view controller or detail view controller", it means the view controller that is currently shown, which in your case is a UITabBarController. But UITabBarController does not implement anything for showViewController() because it doesn't have enough information -- where would it show a view controller? Would it add a new tab, or replace an old one, or what?

So, as a result, you get that fallback, full-screen presentation. I doubt you actually want that user experience.

Later on, when the size changes back to Regular and the split view controller expands, it gets confused by the presentation and eventually crashes. (See what I mean about the defaults not being very good?)

One way to fix this is to implement the delegate method to handle the showDetail. When the width is Compact, explicitly find the view controller you want to put the new view controller onto, and do it. I think you probably want to push onto the nav controller in the first tab:

func splitViewController(splitViewController: UISplitViewController, showDetailViewController vc: UIViewController, sender: AnyObject?) -> Bool
{
    if splitViewController.traitCollection.horizontalSizeClass == .Compact {
        // The default implementation will not handle this properly.
        // Find the appropriate navigation controller and push onto it.
        // It would be better to have a direct outlet to the appropriate navigation controller, 
        // but this will work for an example...

        if let tabBarController = splitViewController.viewControllers.first as? UITabBarController {
            if let navController = tabBarController.viewControllers?.first as? UINavigationController {
                navController.pushViewController(vc, animated: true)

                // we handled the "show detail", so split view controller, 
                // please don't do anything else
                return true
            }
        }
    }

    // we did not handle the "show detail", so split view controller, 
    // please do your default behavior
    return false
}

If you do that, you will also want to implement this delegate method. When the size is changed back to Regular, you will want to handle the "expand" by popping that view controller off of the same nav controller, then returning it:

optional public func splitViewController(
    splitViewController: UISplitViewController
    separateSecondaryViewControllerFromPrimaryViewController
        primaryViewController: UIViewController) -> UIViewController?
like image 116
Kurt Revis Avatar answered Sep 18 '22 06:09

Kurt Revis