views:

1262

answers:

1

Resolution: While trying to recreate this bug in a fresh project to submit to Apple, I discovered that it is specific to iPhone OS 2.1, and compiling for 2.2 fixes the problem. Stephen, thanks for your help; I'll be accepting your answer since it would have worked if the bug still existed or I wasn't willing to compile for 2.2.


I have an app which is radically changing its database schema in a way that requires me to transform old-style records to new-style ones in code. Since users may store a lot of data in this app, I'm trying to display a modal view controller with a progress bar while it ports the data over (i.e. as the very first thing the user sees). This view controller's viewDidAppear: begins a database transaction and then starts a background thread to do the actual porting, which occasionally uses performSelectorInMainThread:withObject:waitUntilDone: to tell the foreground thread to update the progress bar.

The problem is, viewDidAppear: is being called twice. I noticed this because that "start a transaction" step fails with a "database busy" message, but setting a breakpoint reveals that it is indeed called two times—once by -[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:], and again by -[UIViewController modalPresentTransitionDidComplete]. Those names appear to be private UIViewController methods, so I'm guessing this is either a framework bug, or I'm doing something UIKit isn't expecting me to do.

Two relevant code excerpts (some irrelevant code has been summarized):

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    (register some default settings in NSUserDefaults)

    // doing this early because trying to present a modal view controller 
    // before the view controller is visible seems to break it
    [window addSubview:[self.navigationController view]];

    // this is the method that may present the modal view
    [self.databaseController loadDatabaseWithViewController:self.navigationController];

    if(!self.databaseController.willUpgrade) {
     [self restoreNavigationControllerState];
    }
}

And from my DatabaseController class:

- (void)loadDatabaseWithViewController:(UIViewController*)viewController {
    (open the new database)

    (compute the path the old database would live at if it existed)

    if([[NSFileManager defaultManager] fileExistsAtPath:oldDBPath]) {
     (open the old database)

     [viewController presentModalViewController:self animated:NO];
    }
}

So, is there something I'm screwing up here, or should I file a bug report with Apple?

+2  A: 

I saw this in my app too. I never got it entirely confirmed, but I think this is what's happening:

  1. Load root view
  2. Load modal view
  3. OS sends view did appear notification for the view in step 1
  4. The current view controller, which in this instance happens to be your DatabaseController class, picks it up
  5. OS sends the view did appear notification for the modal view
  6. The current view controller gets the notification. In this case it's the exact same controller as last time

In my case I just reset what happened in the first call to viewDidAppear:.

In your case two options spring to mind: a static variable to track whether you've started the upgrade already; or look at the UIView* parameter passed in before starting.

Stephen Darlington
As I noted above, I eventually discovered it was fixed in OS 2.2, but thanks for your suggestion.
Brent Royal-Gordon
No problem. Great to see the "real" answer!
Stephen Darlington