Re: [iPhone] Strange behavior with modal view controllers
Re: [iPhone] Strange behavior with modal view controllers
- Subject: Re: [iPhone] Strange behavior with modal view controllers
- From: Michael Babin <email@hidden>
- Date: Wed, 17 Jun 2009 09:25:44 -0500
On Jun 17, 2009, at 5:43 AM, WT wrote:
Is there any known reason whatsoever why presentModalViewController
would do absolutely nothing?
Possibly, depending on the meaning of "known". In any case, let's
investigate further using the sample project you put together
(referenced in a follow-up message).
In an iPhone game app I'm working on, I have a tab-bar with 5 items,
2 of which are managed by the SettingsViewController and the
GameViewController, respectively. The player can freely switch
between any two views by tapping the appropriate button on the tab-
bar, *except* that - once the game view has been switched into - the
player can only switch between the game view and the settings view.
This allows the player to change settings within the app, but
without being in a game, as well as while playing a game, and
prevents the player from accessing other views that are not supposed
to be accessible during game play.
In order to implement this, I use the tab-bar delegate method -
tabBarController: shouldSelectViewController: to detect when the
player is attempting to switch to the game view, make the game view
controller the active one (directly, not through the tab-bar
controller), release the tab-bar controller, and then return NO:
I'm not 100% certain, but I believe this delegate method is new in
iPhone OS 3.0 (couldn't find it in the 2.2.1 docs). The project you
put together also specifies the iPhone 3.0 SDK. I don't believe it is
kosher to speak of these things on the list at this point, but we can
easily switch to using the iPhone OS 2.2.1 SDK and substitute the -
tabBarController:didSelectViewController: delegate method in their
place.
- (BOOL) tabBarController: (UITabBarController*) tab_bar_controller
shouldSelectViewController: (UIViewController*)
view_controller
{
BaseViewController* curVC = (BaseViewController*)
tabBarController.selectedViewController;
BaseViewController* newVC = (BaseViewController*) view_controller;
NSUInteger kindOfCurVC = curVC.viewControllerKind;
NSUInteger kindOfNewVC = newVC.viewControllerKind;
if (kindOfNewVC == kindOfCurVC)
{
// Since the tab-bar view responds to taps on an already
// selected tab-bar item, and we don't want to do work that
// has already been done, we should return NO in this case.
return NO;
}
else
if (kindOfNewVC == kViewControllerKindGame)
{
// Switching to the game view. Return NO because
// the active view controller will change directly
// to the game view controller and the tab-bar controller
// will never get a chance to switch to the game view.
[tabBarController.view removeFromSuperview];
[window addSubview: gameViewController.view];
[tabBarController release];
tabBarController = nil;
return NO;
}
else
{
// Switching to a view other than the game view.
return YES;
}
}
At this point, it's as if I never had a tab-bar controller. Of
course, I need to have retained both the SettingsViewController and
the GameViewController, which I did when I grabbed them from the tab-
bar view controllers array back in the -
applicationDidFinishLaunching: method.
So, now, when I want to switch between the game and settings views,
I use a modal view controller scheme, with the
SettingsViewController being the modal partner of the
GameViewController. I also have a button on the settings view, that
is hidden when the settings view controller is being managed by the
tab-bar but visible when not. The button's action triggers the
dismissal of the modal behavior, returning to the game view.
Everything works great, except for one thing: the call
[gameViewController presentModalViewController:
settingsViewController animated: YES];
does absolutely nothing! Moreover, I've verified that, after that
call, gameViewController.modalViewController is nil. The AppDelegate
method
- (void) switchFromGameToSettings
{
NSLog(@"AppDelegate: -switchFromGameToSettings");
[gameViewController presentModalViewController:
settingsViewController animated: YES];
NSLog(@"settingsViewController = %@", settingsViewController);
NSLog(@"gameViewController = %@", gameViewController);
NSLog(@"gameViewController.modalViewController = %@",
gameViewController.modalViewController);
// Tell the settings view controller that it is now in-game.
settingsViewController.inGame = YES;
}
(triggered by a tap on the appropriate button on the game view,
which triggers an action method in the game view controller, which
calls -switchFromGameToSettings on the application delegate)
results in:
AppDelegate: -switchFromGameToSettings
settingsViewController = <SettingsViewController: 0xd1b720>
gameViewController = <GameViewController: 0xd1bd40>
gameViewController.modalViewController = (null)
My first thought was that perhaps either one or both of the view
controllers might still be tied up with the tab-bar controller,
somehow, even though I've killed the tab-bar by this point.
I think your first thought is probably correct.
So, I tried a little hack where I create a fresh new pair of view
controllers just prior to the presentModalViewController call, and
now things do work correctly.
A little more investigation then revealed that I don't need to have
a fresh new SettingsViewController object, ie, the one I grabbed
from the tab-bar works just fine. The problem is then with the
GameViewController object.
I suspect that the problem is that your view controllers (Game and
Settings) are left in a "confused" state, with their parent view
controller still referring to the removed and released
UITabBarController. Since the parentViewController and
tabBarController properties of the UIViewController are read-only, you
can attack this from the other end by removing them from the
UITabBarController's list of viewControllers before releasing it. Like
so:
- (void)tabBarController:(UITabBarController *)tab_bar_controller
didSelectViewController:(UIViewController *)newVC
{
if (newVC == gameViewController)
{
[tabBarController.view removeFromSuperview];
[window addSubview: gameViewController.view];
NSMutableArray *tabViewCtlrs = [[tabBarController viewControllers]
mutableCopy];
[tabViewCtlrs removeObject:gameViewController];
[tabViewCtlrs removeObject:settingsViewController];
[tabBarController setViewControllers:tabViewCtlrs];
[tabViewCtlrs release];
tabViewCtlrs = nil;
[tabBarController release];
tabBarController = nil;
}
}
In my modified copy of your sample project, this change seems to do
the trick.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden