Coordinating Controller design pattern in Cocoa Touch

915 views Asked by At

I'm creating an iOS application with lots of custom views, so, using default Cocoa views was not an option. Then, I decided to go with the Coordinating / Mediator Controller design patter (learned in Apress - Pro Objective-C Design Patterns for iOS).

From the delegate, I create a rootViewController pointing to the view in my coordinating controller:

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
coordinatingController = [C6CoordinatingController sharedInstance];
self.window.rootViewController = coordinatingController.activeVC;
[self.window makeKeyAndVisible];
return YES;

Then, in the coordinating controller, I have singleton creation methods:

+ (C6CoordinatingController *) sharedInstance{
if (sharedCoordinator == nil){
    C6Log(@"New Shared Coordinator");
    sharedCoordinator = [[super allocWithZone:NULL] init];
    [sharedCoordinator initialize];
}
else {
    C6Log(@"Return Singleton Shared Coordinator");
}
return sharedCoordinator;
}



+ (id) allocWithZone:(NSZone *)zone{
return [self sharedInstance];
}



- (void) initialize{
C6Log(@"");
[self checkDevice];

_mainVC = [C6MainViewController initWithDevice:device];
_activeVC = _mainVC;
[self checkLanguage];
[self chooseFirstView];
}

I also have a selector to choose the first view (I only have two at this time):

-(void) chooseFirstView{
// If a language was not setted, go to language settings view
if (!language) {
    C6Log(@"Going to Language Settings");
    C6LanguageSettingsViewController *languageVC = [C6LanguageSettingsViewController initWithDevice:device];
    [_mainVC.view addSubview:languageVC.view];
}
else {
    C6Log(@"Going to User Settings", language);
    C6AccountSettingsViewController *accountVC = [C6AccountSettingsViewController initWithDevice:device];
    [_mainVC.view addSubview:accountVC.view];
}
}

Then, I have an IBAction to be used by my views:

- (IBAction) requestViewChangeByObject:(id)object {
int buttonTag = [object tag]; // dividend
int viewTag = buttonTag / divisor; // quotient
int actionTag = buttonTag - (divisor * viewTag); // remainder
C6Log(@"viewTag: %d | actionTag %d", viewTag, actionTag);
switch (viewTag) {
    case LanguageTags:
        C6Log(@"LanguageTags");
        break;
    case AccountTags:
        C6Log(@"AccountTags");
        break;
    default:
        break;
}

In the NIB, I created an Obect (Coordinating Controller) an I call the IBAction from there. It works just fine and I can change my views (it still needs to be implemented)………

BUT… I also want to change the language, but, as it is not a navigation issue, I wanto to do it from the C6LanguageSettingsViewController and not from the C6CoodinatingController.

So, I created another IBAction in the C6LanguageSettingsViewController:

- (IBAction)chooseLang:(id)sender{
UIImage *bt;
[self resetImagesToNormalState];
C6Log(@""); 
C6Log(@"%@", [sender tag]);
C6Log(@"%@", sender);
.
.
.

When I connect the button to this IBAction (through File's Owner OR via an LanguageSettingsViewController object), the app breakes and, sometime it shows no error and, sometimes it presents Unrecognized selector sent to instance OR EXC_BAD_ACCESS (Code=1, address = 0x………) in the UIApplicationMain.

I believe the problem is the NIB doesn't find the file's owner… but I'm not sure how to solve it.

1

There are 1 answers

0
Eduardo Russo On BEST ANSWER

Ok… it seems absurd, but I'm answering myself :P

I managed to make it work (finaly!) and I found I was managing the viewControllers in a BAAAAD way, so, I changed some code in the coordinating controller:

First, I don't have a "real" mainViewController with NIBs and stuf no more…

OLD initialize

- (void) initialize{
    C6Log(@"");
    [self checkDevice];
    _mainVC = [C6MainViewController initWithDevice:device];
    _activeVC = _mainVC;
    [self checkLanguage];
    [self chooseFirstView];
}

NEW initialize

- (void) initialize{
    C6Log(@"");
    [self checkDevice];
    [self checkLanguage];
    [self chooseFirstView];
}

checkDevice verifies if it's iPhone or iPad, so I can choose the right NIB.

checkLanguage checks the [NSUserDefaults standardUserDefaults] for the language

Finaly, I call chooseFirstView:

OLD chooseFirstView

-(void) chooseFirstView{
    // If a language was not setted, go to language settings view
    if (!language) {
        C6Log(@"Going to Language Settings");
        C6LanguageSettingsViewController *languageVC = [C6LanguageSettingsViewController initWithDevice:device];
        [_mainVC.view addSubview:languageVC.view];
    }
    else {
        C6Log(@"Going to User Settings", language);
        C6AccountSettingsViewController *accountVC = [C6AccountSettingsViewController initWithDevice:device];
        [_mainVC.view addSubview:accountVC.view];
    }
}

NEW chooseFirstView

-(void) chooseFirstView{
    // If a language was not setted, go to language settings view
    _activeVC = [[UIViewController alloc] init];
    UIImage *bgImage = [UIImage imageNamed:@"bg.png"];
    UIImageView *bgView = [[UIImageView alloc] initWithImage:bgImage];
    [_activeVC.view addSubview:bgView];

    if (!language) {
        C6Log(@"Going to Language Settings");
        languageVC = [C6LanguageSettingsViewController initWithDevice:device];
        [_activeVC.view addSubview:languageVC.view];
    }
    else {
        C6Log(@"Going to User Settings", language);
        accountVC = [C6AccountSettingsViewController initWithDevice:device];
        [_activeVC.view addSubview:accountVC.view];
    }
}

The big change is WHEN and HOW I initiated the _activeVC… AND the fact that both _languageVC and _accountVC are now global variables.

Well, after this changes, the NIB button call both IBAction methods: it's file's owner and the coordinating controller.

Another BIG thing about using this kind of pattern is how to change from one view to another without explode iOS device memory… here's how I do it inside the coordinating controller:

- (IBAction) requestViewChangeByObject:(id)object {
    int buttonTag = [object tag]; // dividend
    int viewTag = buttonTag / divisor; // quotient
    int actionTag = buttonTag - (divisor * viewTag); // remainder
    C6Log(@"ViewTag: %d", viewTag);
    switch (viewTag) {
        case LanguageTags:{
            C6Log(@"LanguageTags - button %d", actionTag);
            accountVC = [C6AccountSettingsViewController initWithDevice:device];
            UIView *fromView = languageVC.view;
            UIView *toView = accountVC.view;
            [self switchFrom:fromView To:toView usingAnimation:AnimationPushFromRigh];
        }
            break;
        case AccountTags:{
            C6Log(@"AccountTags - button %d", actionTag);
            switch (actionTag) {
                case 0:{
                    C6Log(@"Go back");
                    languageVC = [C6LanguageSettingsViewController initWithDevice:device];
                    UIView *fromView = accountVC.view;
                    UIView *toView = languageVC.view;
                    [self switchFrom:fromView To:toView usingAnimation:AnimationPushFromLeft];
                }
                    break;

                default:
                    break;
            }
        }
            break;
        default:
            break;
    }
}

In the beginning of the method, I do a lot of math… I "created" a pattern where each NIB should have it's tags beginning with 100 multiples… so, language begins with 0, account with 100………

#define divisor         100
#define LanguageTags    0
#define AccountTags     1

Then, the way I change from one view to another is right there:

-(void) switchFrom:(UIView*) fromView To:(UIView*) toView usingAnimation:(int) animation{
    C6Log(@"");
    /*************** SET ALL DEFAULT TRANSITION SETTINGS ***************/
    // Get the current view frame, width and height
    CGRect pageFrame = fromView.frame;
    CGFloat pageWidth = pageFrame.size.width;
    // Create the animation
    [UIView beginAnimations:nil context:nil];
    // Create the delegate, so the "fromView" is removed after the transition
    [UIView setAnimationDelegate: fromView];
    [UIView setAnimationDidStopSelector:@selector(removeFromSuperview)];
    // Set the transition duration
    [UIView setAnimationDuration: 0.4];

    // Add the "toView" as subview of "fromView" superview
    [fromView.superview addSubview:toView];

    switch (animation) {
        case AnimationPushFromRigh:{
            // Position the "toView" to the right corner of the page            
            toView.frame = CGRectOffset(pageFrame, pageWidth,0);
            // Animate the "fromView" to the left corner of the page
            fromView.frame = CGRectOffset(pageFrame, -pageWidth,0);
            // Animate the "toView" to the center of the page
            toView.frame = pageFrame;
            // Animate the "fromView" alpha
            fromView.alpha = 0;
            // Set and animate the "toView" alpha
            toView.alpha = 0;
            toView.alpha = 1;

            // Commit the animation
            [UIView commitAnimations];
        }
            break;
        case AnimationPushFromLeft:{
            // Position the "toView" to the left corner of the page         
            toView.frame = CGRectOffset(pageFrame, -pageWidth,0);
            // Animate the "fromView" to the right corner of the page
            fromView.frame = CGRectOffset(pageFrame, pageWidth,0);
            // Animate the "toView" to the center of the page
            toView.frame = pageFrame;
            // Animate the "fromView" alpha
            fromView.alpha = 0;
            // Set and animate the "toView" alpha
            toView.alpha = 0;
            toView.alpha = 1;

            // Commit the animation
            [UIView commitAnimations];
        }
            break;
        default:
            break;
    }
}

I really hope this helps who's trying to use this coordinating controller pattern :P