Polymorphism With Control
- Demo
- Polymorphism with Controllers in Matchismo
- How to change the Class of a controller in a storyboard
- Multiple MVCs in an application
UINavigationController
UITabBarController
- Demo
- Attributor stats
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
- Why?
- When your application gets more feature than you can fit in one MVC.
- How to add a new MVC to your storyboard
- Drag "View Controller" from object palette.
- Create a subclass of
UIViewController
using New File menu item. - Set that subclass as the class of your new Controller in the attributes inspector.
- How to present this new MVC to the user
UINavigationController
UITabBarController
- Other mechanisms we'll talk about later in the course (popover, modal, etc.).
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
- Let's talk about how the segue gets set up first
- Then we'll look at how we create a
UINavigationController
in our storyboard.
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
- When does a pushed MVC pop off?
- Usually because the user presses the "back" button (shown in the previous demo
- But it can happen programmatically as well with this
UINavigationController
instance method - (void)popViewControllerAnimated:(BOOL)animated;
- This does the same thing as clicking the back button.
- Somewhat rare to call this method. Usually we want the user in control of navigating the stack.
- But you might do it if some action that the takes in a view makes it irrelevant to be on screen.
- Example
- Let's say we push and MVC which displays a database record and has a delete button with this action:
-
- (IBAction)deleteCurrentRecord:(UIButton *)sender { // delete the record that we are displaying // we just deleted the record that we are displaying! // so it does not make sense to be on screen any more, so pop. [self.navigationController popViewControllerAnimated:YES; }
View Controller
- Other kinds of segue besides Push
- Replace - Replaces the right-hand side of a
UISplitViewController
(iPad only) - Popover - Puts the view controller on the screen in a popover (iPad only)
- Modal - Puts the view controller up in a way that blocks the app until it is dismissed.
- Custom - You can create your own subclasses of UIStoryboardSegue
- We'll talk about iPad-related segue in future lectures
- Replace & Popover
- We'll talk about Modal segue later on too
- People often use Modal UIs as a crutch, so we don't want to go to that to early.
Segue
- When a segue happens, what goes on in my code?
- The segue offers the source VC the opportunity to "prepare" the new VC to come on screen.
- This method is sent to the VC that contains the button that initiated the segue.
- This is called between awakeFromNib and viewDidLoad, which we need to keep in mind.
-
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"DoSomething"]) { if ([segue.destinationViewController isKindOfClass:[DoSomethingVC class]]) { DoSomethingVC *doVC = (DoSomethingVC *)segue.destinationViewController; doVC.neededInfo = …; } } }
- You should pass data that the new VC needs here and "let it run".
- Think of the new VC as part of the view of the Controller that initiated the segue.
- It must play by the same rules as a View.
- For example, it should not talk back to you (except though blind communication like delegation).
- You can prevent a segue from happening
- Your controller usually always just segues.
- But if you respond
NO
to this method, it would prevent the identified segue from happening. -
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { if ([segue.identifier isEqualToString:@"DoAParticularThing"]) { return [self canDoParticularThing] ? YES : NO; } }
- Do not create a "dead UI" with this (e.g. buttons that do nothing).
- This is a very rare method to ever implement, perhaps in cases where you would display an alert and block the segue.
Demo
- Attributor stats
- Use a
UINavigationController
to show "statistics" on colors and outlining in Attributor.
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.