up

Polymorphism With Control

Demo

The Lesson 3 matchismo game has a specificity issue in its main view controller, we have had to use #import "PlaingCardDeck.h" where we would prefer to have imported "Deck.h".

By removing the use of PlayingCardDeck.h we make this controller class abstract, there is no way to define this with the language so it has to be documented with the class that it is to be subclassed with certain methods that must be overridden, as such we must also now make those methods public.

- (Deck *)createDeck // abstract
{
    // return [[PlayingCardDeck alloc] init];
    return nil;
}

We can now create a subclass of our view controller that overrides createDeck returning a PlayingCardDeck. In xcode we need to set the nib file to be owned by the subclass with the overridden method, for our game to work.

Multiple MVCs

UINavigationController

MVCs Working Together

If the relationship between two MVCs is "more detail", then we use a UINavigationController to let theme share the screen. It is generally a sparsely populated full screen with a title, it is special because we can set its rootViewController to another MVC, which will then embed the second MVC inside its own view.

Then a UI element within this view (e.g. a UIButton can segue to the other MVC and its View will now appear in the NSNavigationController instead. We call this kind of segue a "push segue".

The back button in this situation will automatically appear, when we press it we'll go back to the first MVC.

It is important to understand that when we navigate in this way the different views are allocated and deallocated every time we change screen. If there is any data on screen that needs to be kept, we have to save it somewhere when the back button is pressed.

Segue

So far we have only had one MVC in the application creating a second is as easy as dragging a "View Controller" from the object library in the interface builder, onto a blank part of the screen, then setting its class.

These individual views of controllers on the storyboard are known as scenes. To create a segue between to scenes we can drop a button onto one of the scenes and then ctrl drag from that button onto the target scene. Upon letting go of the mouse a dialogue box then opens to ask which type of segue we want.

An arrow now appears between our two scenes which once selected displays the details of our segue in the inspection panel on the right, from where we can begin to configure our transition, the identifier in particular is the link between xcode's interface builder and our code.

For this basic application to work, we will need an third MVC controller, to display the first two Views permitting the segue.

You can embed a View Controller in a UINavigationController by selecting the View Controller that you want to be the root, then choosing Editor > Embed In > Navigation Controller from the Editor drop down menu.

Now we have three MVC scenes in the interface builder! There is an arrow pointing towards this new scene, this arrow represents what will be displayed on screen upon the applications launch, it can be picked up an moved to any scene that we want. Sometimes in testing it can be nice to start directly in a specific place in the applications UI tree.

Buttons added to the navigation bar, if one is present, must be UIBarButtons, as UIButtons can't be placed inside of the navigation bar.

NSNavigationControler

View Controller

Segue

Demo

Drag a View Controller into the projects main storyboard screen and then create a new TextStatsViewControler class. In that class add a public property called textToAnalyze that is an NSAttributedString. In that properties setter add a call to update the UI and then write a method for this. We also need to add a call to the same updateUI method inside of viewWillAppear. Now with updateUI being called in two locations we can save a little processing by wrapping the call in the text property setter to update only if the view is currently being displayed, using [self.view.window].

- (void)setTextToAnalyze:(NSAttributedString *)textToAnalyze
{
    _textToAnalyze = textToAnalyze;
    // If I am currently displayed on screen, update the UI
    if (self.view.window)[self updateUI];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self updateUI];
}

- (void)updateUI
{

}

Next if we are going to update our text analyser we will first need a fuction that will do the analyisis, we will use this simple function for counting NSAttributedText text attributes.

- (NSAttributedString *)charactersWithAttribute:(NSString *)attributeName
{
    NSMutableAttributedString *characters = [[NSMutableAttributedString alloc] init];
    
    NSUInteger index = 0;
    while (index < [self.textToAnalyze length]) {
        NSRange range;
        id value = [self.textToAnalyze attribute:attributeName atIndex:index effectiveRange:&range];
        if (value) {
            
            BOOL shouldAppend = YES;
            
            // If checking for foreground color, ensure it's not black
            if ([attributeName isEqualToString:NSForegroundColorAttributeName]) {
                if ([value isKindOfClass:[UIColor class]]) {
                    UIColor *color = (UIColor *)value;
                    if ([color isEqual:[UIColor labelColor]]) {
                        shouldAppend = NO; // Skip black characters
                    }
                }
            }

            if (shouldAppend) {
                [characters appendAttributedString:[self.textToAnalyze attributedSubstringFromRange:range]];
            }
            
            index = range.location + range.length;
        } else {
            index++;
        }
    }
    
    return characters;
}

From our updateUI funtion, we can now scan our text that is to be analised for any attributes that we desire.

- (void)updateUI
{
    [self.colorfulCharactersLabel setText:[NSString stringWithFormat:@"%lu colorful characters",
     [[self charactersWithAttribute:NSForegroundColorAttributeName] length]]];
    [self.outlinedCharactersLabel setText:[NSString stringWithFormat:@"%lu outlined characters",
     [[self charactersWithAttribute:NSStrokeWidthAttributeName] length]]];
}

We can now wrap our main scene by embedding it inside of a navigation view controller into which we can set a nav bar and button icon, by dragging that button icon into our second scene we generate a segue.

The only task remaining is to set out text to be analysed into the storyboards NSSeguePerforming protocols method prepareForSegue:, triggering our text analysis by setting the text to be analysed property within our text stats view controller …

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"Analyze Text"]) {
        if ([segue.destinationViewController isKindOfClass:[TextStatsViewController class]]) {
            TextStatsViewController *tsvc = (TextStatsViewController *)segue.destinationViewController;
            tsvc.textToAnalyze = self.body.textStorage;
        }
    }
}

UITabBarController

A good example of the UITabBarController is the clock app which uses a tab bar to bring together four different MVC views, each of its tabs are completely independent of the others, this is the essential definition as to what MVC controllers inside of a tab bar should be; Completely independent of each other. Whereas if they are dependant, you want a navigation controller or on an iPad perhaps a popover.

The UITabBarController is essentially an array of View Controllers and in a story board can be setup as simply as by using ctrl drag. The bar itself will contain tab bar icons, that each contain the title of a View Controller, but in most scenarios you will not make these yourself, they are create in and by the storyboard.

Creating a tab bar

You can create a tab bar by similarly embedding in an existing View Controller, this time selecting tab bar instead of navigation controller. Generating your tab bar controller in the storyboard, View Controllers can be added to the tab bar by ctrl dragging from the Tab Bar Controller in the storyboard onto your desired View Controller also in the storyboard, and selecting the Relationship View Controller segue type.

It is OK to put a navigation bar controller inside a tab controller, but not the other way around; You should never put a tab controller inside of a navigation controller.