Things wot I learnt from Stanford – iPad & iPhone App Development – Fall 2011 – Lecture 6

Multiple MVC’s and Segues

A common design pattern is for an object to pass a reference to itself (using ‘self’) when sending a message to a delegate so that the delegate can ask our object for more information if they want. For example in the @protocol definition shown below, the FaceView control will set the sender parameter to self when it calls the delegate which handles the smileForFaceView: message:

Fig 1

An object sends a reference to itself to the handler.

If you want the fact that your class implements a @protocol to be kept private (i.e. it’s not of interest to an external developer), you are allowed to specify the @protocol name in your private @interface declaration  – i.e. the one (normally) in your “.m” file rather than in the header “.h” file. See screenshot.

Fig 2

Declaring a protocol implementation privately

CGPointZero is a handy, predefined constant for specifying a point of (0, 0) for example when resetting translations passed to gesture handlers. See screenshot.

Fig 3

Using CGPointZero to reset a translation in a gesture recognizer.

One MVC (Model, View Controller) should never control more than one screen. If we need to add another screen we should add a new MVC.

Fig 4

Multiple MVCs working together to show multiple screens.

We need a “controller of controllers” to switch between multiple MVCs (or screens) for example a UINavigationController. These are sometimes referred to as “container controllers”.

Fig 5

A UINavigationController acts as a “controller of controllers” and allows us to co-ordinate between multiple MVCs

Fig 6Fig 7A UINavigationController is a subclass of UIViewController with its own view which can have a title bar and always has an area below it in which to show the view from another MVC. It has no model, it is purely a UI controller.

It has a rootViewController @property which can point to another MVC. This controls what appears in its view (the white area in the screenshots above). It actually “embeds” that MVC’s view into this area. It will change the bounds of your MVC’s view to fit (note your “springs” and “struts” defined in Interface Builder and the view’s contentMode therefore become important during this resizing).

Segues

Only in iSO 5 and later. To create a segue in your Storyboard, hold down Ctrl and drag from the source object (e.g. a button) to the destination view controller.

Fig 8

Creating a Segue

Segues always instantiate a new instance of a view controller. Segues ALWAYS instantiate a NEW instance of a view controller. That’s not a Ctrl-V stutter- I said it twice as it’s important! They always create a new instance from scratch. This allows iOS to dispose of the destination view controllers and free up memory as soon as they’re dismissed.

Segue styles (at the time of writing) can be Push, Modal or Custom (your own subclass of UIStoryboardSegue). Push is used inside a UINavigationController. The iPad also supports Replace which allows you to replace the right-hand view of a UISplitViewController. The style can be set in the Storyboard by selecting the segue and choosing it from a list as in the slide below.

Fig 9

Specifying a segue style (and identifier)

The slide below shows how you can embed your view controller (often your startup one) in a Navigation Controller using the Storyboard interface. The process is similar for embedding within a Tab Bar Controller.

Fig 10

Embedding your view controller in a Navigation Controller.

This leads to this…

Fig 11Your view controller is added to the Navigation Controller as the rootViewController @property (an outlet).

Fig 12Here’s an example of how your view could appear embedded within a Navigation Controller. Notice that the “back” button takes it’s label text from the title @property of the previous UIViewController and the title of the current View Controller is shown at the top.

Fig 13

Using a UINavigationController

A really quick way to set the title @property is just to double-click it directly in the Storyboard and type it in.

Fig 14

Entering the title @property for your View Controller.

Pressing the “Back” button pops a pushed MVC back to the original, source MVC, but you can also do this programmatically using the UINavigationController instance method:

– (void) popViewControllerAnimated: (BOOL) animated

It’s rare but sometimes handy to call this. In this example, we actually call this function from within the controller on screen – i.e. the one to be “popped” – when its display is no longer required.

Fig 15

Popping a view controller programmatically.

All UIViewControllers have a navigationController @property. If the controller is not embedded in a Navigation Controller this will be set to nil. There are similar properties to access other container controllers such as tabBarController.

Triggering a segue from code:

Fig 16When a segue fires, the destination view controller is instantiated with all its outlets then the following message is sent to the source view controller to give it the chance to pass it information:Fig 17A common pattern here is to set ‘self‘ as the delegate for the the new, destination controller using the id type. Using the id type is important! DO NOT PASS THE ACTUAL TYPE. Always treat the new View controller as you would treat a view – give it as little information as possible. This approach greatly enhances the portability (“re-usability”) of your controller and code.

You can also instantiate a view controller programmatically using the Storyboard method:

– (id)instantiateViewControllerWithIdentifier:(NSString *)identifier

You can obtain a reference to the Storyboard by calling self.storyboard – as long as self was created from the same Storyboard.

Fig 18

Instantiating a view controller from the Storyboard programmatically.

Note the use of pushViewController in this example which assumes the current controller is embedded in a Navigation Controller.

Tip: Did you know a UILabel control can have more than one line of text simply by selecting it in your Storyboard and setting the “Lines” attribute to a number greater than 1?

Advertisements

Things wot I learnt from Stanford – iPad & iPhone App Development – Fall 2011 – Lecture 5

Protocols and Gestures

When rotated, the frame of all sub views in your controller’s view will be adjusted based on their “struts & springs”.

You define the struts & springs in the Interface Builder:

Slide: Struts and Springs

Defining Struts and Springs in Interface Builder.

The outer, white rectangle in the preview represents the superview. The inner, red box is the view you’re defining your struts and springs for.

By default, after a rotation, drawRect is NOT called – instead the “bits” of any custom drawing in your view will be stretched or squished or moved according to a UIView @property contentMode:

@property (nonatomic) UIViewContentMode contentMode;

There are many possible values for contentMode all prefixed UIViewContentMode including Left, Right, Top, Bottom, BottomLeft, BottomRight, TopLeft and TopRight

These modes have a similar effect to struts and springs i.e. they move your bits to that location but the contentMode is actually applied AFTER any struts and springs.

There are also some content modes which describe how you want your bits to scale UIViewContentModeScaleToFill, UIViewContentModeAspectFill, UIViewContentModeAspectFit.

UIViewContentModeScaleToFill is the default and is the reason why, if you don’t specify a mode, any drawings can become elongated or squished into a different aspect ratio – like your gran’s new TV.

contentMode UIViewContentModeRedraw ensures drawRect is called. This is usually what you would want if you have custom drawing.

There is another @property:

@property (nonatomic) CGRect contentStretch

This allows you to control which bits get stretched and which don’t. This is useful for example if you have a button where you’d like the middle part stretched but the ends to stay the same.

Note that you can set the contentMode in code usually when your view is initialized, or you can simply set it using a drop down in Interface Builder – select your view and navigate to the Attributes Inspector – the list is labelled “Mode”.

Storyboards and UIView Initialization

Warning: when a view is initialized from a nib file or Storyboard, initWithFrame (the designated initializer) DOES NOT GET CALLED. Instead, awakeFromNib is called. If you have a habit of putting setup code in initWithFrame you need to be aware if this. Move your setup code into a separate method and call from both as per the slide.

Warning: awakeFromNib

Call setup() from Warning: awakeFromNib: AND initWithFrame:

Note it’s neater to put your setup stuff in property setters or getters rather than doing this. If you’re the lazy kind, you could also put it in viewDidLoad and avoid the issue altogether (depending on what your setup does).

Protocols

It is considered good practice to always include <NSObject> as a required protocol in any protocol definition. This ensures that only descendants of NSObject can use your protocol since descendants of NSObject will, by inheritance, handle the <NSObject> protocol. Since we mainly work with NSObject descendants in Objective-C this could also be considered a waste of typing unless you’re a pedant – but we all are aren’t we? 😉

Protocols - Syntax

The syntax for defining Protocols

Protocols are defined in a header (.h) file.

A Protocol can be the only thing in a standalone header file – handy for sharing between many objects. Often though a Protocol will be in the header file of the class that wants other classes to implement it. For example the UIScrollViewDelegate Protocol is defined in UIScrollView.h along with the definition for the UIScrollView itself.

You can refer to an object that implements a protocol using the <Protocol> specifier e.g.:

id <Foo> obj = [[MyClass alloc]init];

This allows the compiler to alert you with warnings if you assign any object that doesn’t implement <Foo>. You can also use this in arguments and @property declarations (see slide).

Declaring Protocol Implementers

Declaring Protocol Implementers for the sake of the compiler.

A view should never need to know the class of its controller.

The most common use of a Protocol is for delegates. A delegate or dataSource is almost always defined as a weak @property.

@property (nonatomic, weak) id <UISomeObjectDelegate> delegate;

This is common sense really since the object serving as a delegate will usually outlive the object doing the delegating – especially when the delegator is a view (such as UIScrollView) since the delegate is that view’s controller. Controllers always create and clean up their views. Views are just their minions. They are Gaga’s little monsters.

A dataSource is just like a delegate in that we’re delegating the provision of data. Views often have a dataSource delegate as they cannot own their data.

Gesture Recognizers

Gesture recognizers are usually added by the controller and handled by the view – adding the gesture recognizer is the equivalent to “switching it on”. Views often “know” what to do with the gesture e.g. “pinch”. If a view doesn’t make sense without the gesture then it may even add its own gesture recognizer as well as handling it (e.g. a UIScrollView). Even in this case, the handler may instead be handled by the controller if you want to handle things differently – e.g. a gesture that effects the model since the view should not modify the model directly.

The initWithTarget: argument of a gesture recognizer actually specifies the gesture handler NOT the object on which the gesture will operate – that is specified by the receiver of the addGestureRecognizer message.

A reference to the gesture recognizer itself is sent to the handler so it can be examined for its state. Every gesture recognizer has a readonly “state” property of type UIGestureRecognizerState.

The Initial value of state is “Possible”, another value is “Recognized”. Discrete gestures such as tap can go directly to this state. Continuous gestures like pan can also have states of “Began”, “Changed” and “Ended”. At any time, the state can change to “Failed”, for example if a phone call interrupts a gesture, or “Cancelled” if the recognizer realizes its not a gesture after all.

Handling the pan gesture (slide)

Handling the pan gesture

A Nice Tip

If your custom view has a writable property, the value of which can effect any displayed, custom drawing (e.g. a “scale” property) a common pattern is to call [self setNeedsDisplay] in the property setter. This queues up a call to drawRect: which will redraw your view with the new property value – neato!! TIP: Wrap this in a check which makes sure that setNeedsDisplay is only called if the property value has actually changed. Calls to drawRect can be expensive so shouldn’t be done if not necessary!

Tip: Calling setNeedsDisplay in your setter (slide)

Tip: Calling setNeedsDisplay in your setter