Spec
Spec

Reputation: 445

Alternate between ANY UIViewControllers?

This question is LONG, so please be patient.

As we know there are two ways to show a controller: push or present. there are also two composite controllers: tabbarcontroller & navigation controller. with these can make complicated viewcontrollers tree. Now i have a complicated viewcontroller tree like this.

T: tabbarcontroller N: navigationcontroller V: normal viewcontroller P: push S: present

So PV2 means push a viewcontroller , this are six combinations ,but PN is forbiddened, you cannot push a navigation controller. and as i research

  T1
  ---------------------
  N1      N2      N3
  |                |
  PV1             PV3
  |
  PV2

now the current view controller is V2. i wanna to jump to N3 and push a V3. i wrote this code:

[self.navigationcontroller popToRootViewControllerAnimated:NO]; // No is important
tabbarcontroller.selectIndex = 2;
[N3 pushViewController:V3];

it works, but still bad, too bad. 1: N3 must know v3, the coupling between viewcontrollers is strong. 2: it cannot work in complicated situations…also cause animation problems.

A friend of mine told me they made a PageConductor that can alternate between ANY viewcontrollers easyily. that really confused me...

Upvotes: 0

Views: 76

Answers (1)

hoha
hoha

Reputation: 4428

  1. What's wrong with N3 knowing about V3? Did you mean V2 knowing about V3? Anyway I see a much bigger problem in V2 knowing about T. If you really have to do this complex screen flow delegate it to a dedicated object (like PageConductor your friend mentioned). Talk to this object in terms of user actions and not controller manipulations (like [screens showUserProfile:userId] or something), this will give you a real decoupling between controllers.

  2. What are the 'complicated situations' examples? As for animation problems in this particular case try to switch to T3 before popping T1 stack.

Update

A small contrived example of screen manager of some imaginary app.

@implementation ScreenManager {
}

@synthesize tabController = _tabController;

- (BOOL)goToUserList
{
    // user list is a root controller of tab 0 navigation controller
    BOOL switched = [self switchToTab:0];
    UINavigationController *nc = [self rootNavigationAtTab:0];
    [nc popToRootViewControllerAnimated:!switched]; // we should animate popping if we didn't change tabs
    return switched;
}

- (BOOL)showUserProfile:(NSUInteger)userId
{
    BOOL switched = [self goToUserList];
    UIViewController *uc = [[UserDetailsController alloc] initWithUserId:userId];
    UINavigationController *nc = [self rootNavigationAtTab:0];
    [nc pushViewController:uc animated:YES];
    return switched;
}

- (BOOL)showMapAtLocation:(CLLocation *)location
{
    MapController *mc = [self downcast:[self rootControllerAtTab:1] to:[MapController class]];
    mc.location = location;
    return [self switchToTab:1];
}

/* returns if we actually switched tabs as a result of this action */
- (BOOL)switchToTab:(NSUInteger)tabIdx
{
    NSUInteger prevTabIdx = _tabController.selectedIndex;
    _tabController.selectedIndex = tabIdx;
    return prevTabIdx != tabIdx;
}

- (BOOL)pushController:(UIViewController *)controller animated:(BOOL)animated toTab:(NSUInteger)tabIdx
{
    BOOL switchedTabs = [self switchToTab:tabIdx];
    UINavigationController *nc = [self rootNavigationAtTab:tabIdx];
    [nc pushViewController:controller animated:animated];
    return switchedTabs;
}

- (UINavigationController *)rootNavigationAtTab:(NSUInteger)tabIdx
{
    return [self downcast:[self rootControllerAtTab:tabIdx] to:[UIViewController class]];
}

- (id)downcast:(id)obj to:(Class)klass
{
    return [obj isKindOfClass:klass] ? obj : nil;
}

- (UINavigationController *)rootControllerAtTab:(NSUInteger)tabIdx
{
    return [_tabController.viewControllers objectAtIndex:tabIdx];
}

+ (ScreenManager *)currentManager
{
    static ScreenManager *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    manager = [ScreenManager new];
});

    return manager;
}


@end

Here we have 3 actions available to user - "Show user profile by ID", "Show user list" and "Show some location on the map". One may tweak this basic solution to meet their application requirements but the idea it to decouple tab/screen management logic from local controller logic. All actions return a boolean indicating if tabs was switched during it so client code can make some tweaks depending on this (like popping its navigation stack in case tab was changed).

Upvotes: 1

Related Questions