I have a strange problem and I don't know if this is my mistake (most likely) or a mistake in the UINavigationController.
I am using UINavigationController in my application. In some cases, I need complex navigation programs such as "pop 2 screen and push new one". I am currently doing this, getting the current navigationController.viewControllers , modifying the collection, and [navigationController setViewControllers:newStack animated:YES] .
It happens that this very often leads to an application crash. Typically, accidents are SIGBUS or SIGSEGV. The steps for playback are as follows:
- Make one of these difficult navigations.
- Go back one or two times depending on the type of navigation
- Crash!
The interesting things are:
- If the navigation was just βa few screens back,β then the next inverse error. If the navigation was βa few screens back and tap the new screen,β then the second back will work. Moreover, in such cases, navigators, such as back, forward, backward, backward, usually do not cause the application to crash
- The strangest thing: if I do
[navigationController popToRootViewControllerAnimated:NO] before updating the stack, the application does not crash or at least crashes much less often (I could not reproduce the crash).
An example of a trace of the crash stack captured by my signal handler:
- 0x0027a9 mysighandler ()
- 0x3293d82b _sigtramp ()
- 0x31c59065 - [UIApplication sendAction: to: from: forEvent:]
- 0x31c59005 - [UIApplication sendAction: toTarget: fromSender: forEvent:]
- 0x31c58fd7 - [UIControl sendAction: to: forEvent:]
- 0x31c58d31 - [UIControl _sendActionsForEvents: withEvent:]
- 0x31c59645 - [UIControl touchesEnded: withEvent:]
- 0x31c5865d - [UIWindow _sendTouchesForEvent:]
- 0x31c58039 - [UIWindow sendEvent:]
- 0x31c5492f - [UIApplication sendEvent:]
- 0x31c543a7 _UIApplicationHandleEvent ()
- 0x3352c9ed PurpleEventCallback ()
- 0x3358ac2d CFRunLoopRunSpecific ()
- 0x3358a35d CFRunLoopRunInMode ()
- 0x3352bb33 GSEventRunModal ()
- 0x3352bbdf GSEventRun ()
- 0x31c1976f - [UIApplication _run]
- 0x31c18473 UIApplicationMain ()
- 0x00214d main ()
- 0x0020c4 start ()
Other:
- 0x002945 mysigandler ()
- 0x3293d82b _sigtramp ()
- 0x31c5ead3 - [UIScrollView _updatePanWithStartDelta: event: gesture: ignoringDirectionalScroll:]
- 0x31c5e435 - [UIScrollView handlePan:]
- 0x31d14651 - [UITableView handlePan:]
- 0x33590da7 - [Protocol selects: withObject:]
- 0x31c428b5 - [UIGestureRecognizer _updateGestureWithEvent:]
- 0x31c427a9 - [UIGestureRecognizer _updateGestureStateWithEvent: afterDelay:]
- 0x31c583d5 - [UIWindow _sendGesturesForEvent:]
- 0x31c5802b - [UIWindow sendEvent:]
- 0x31c5492f - [UIApplication sendEvent:]
- 0x31c543a7 _UIApplicationHandleEvent ()
- 0x3352c9ed PurpleEventCallback ()
- 0x3358ac2d CFRunLoopRunSpecific ()
- 0x3358a35d CFRunLoopRunInMode ()
- 0x3352bb33 GSEventRunModal ()
- 0x3352bbdf GSEventRun ()
- 0x31c1976f - [UIApplication _run]
- 0x31c18473 UIApplicationMain ()
- 0x0022e9 main ()
- 0x002260 start ()
Example of "complex" navigation:
@implementation UINavigationController(MyCategory) - (void)popViewControllers:(NSInteger)count { NSArray* oldList = self.viewControllers; NSMutableArray* newList = [NSMutableArray arrayWithArray:oldList]; if(count > [oldList count]) { CLogError(@"Poping %d screens when there is only %d", count, [oldList count]); count = [oldList count] - 1; } for(int i = 0; i<count; i++) { [newList removeLastObject]; } [self setViewControllers:newList animated:YES]; } @end
Anyone now, what can I do wrong? I just don't have enough ideas.
Adding
I ran my application using NSZombieEnabled and MallocStackLogging to find out which object is failing. However, this did not give my reasonable results. To trace stack # 1, it is not executed in step 3 ( -[UIApplication sendAction:to:from:forEvent:] ), and the zombie object -[UIBarButtonItem performSelector:withObject:withObject:]: message sent to deallocated instance 0xa5f5f90 . This is the right navigation bar on the screen, to which the application takes 2 screens back (and remember that this work with two screens back works, only the next "normal" reverse navigation fails). But I am not doing anything with this button. Relevant code in ViewControler initWithSomething:(Something*)something :
UIBarButtonItem* doneItem = [[UIBarButtonItem alloc] initWithTitle:@"Complete" style:UIBarButtonItemStyleDone target:self action:@selector(onDone)]; self.navigationItem.rightBarButtonItem = doneItem; [doneItem release];
The only feature of this button is that the onDone selector does this two-screen navigation backwards, but I donβt think it really matters. Therefore, I believe that something is wrong on the object of a higher level (perhaps View Controller or UINavigationController?). But what is wrong?
Addendum October 4, 2011:
Since people are still sometimes looking for this question, here is some code. My current approach to this problem is to use a custom subclass of UINavigationController instead with the following hack (no guarantees that this works or is still necessary):
@interface CustomUINavigationController : UINavigationController { } @end @implementation CustomUINavigationController - (void)setViewControllers:(NSArray*)newStack animated:(BOOL)animated {
Another important thing: itβs better not to start animated navigation while the previous animation navigation is still ongoing (i.e. at least until calling the WillAppear view :).