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 4

This lecture was mainly to introduce views.

If you expose a public method that accepts an id, use introspection to check that it is what you expect e.g. if ([theParam isKindOfClass:[NSArray class]]) {…do your stuff…}

Any view has only one superview which can be referenced via the (UIView *)superview @property.

A view can have many (or no) subviews, which can be accessed via the (NSArray *)subviews property. Note that those views later in the array are on top of earlier ones (z-order wise).

In iOS, there will only be one UIWindow right at the top of the view hierarchy. You almost never need to interact with this. Normally the highest view you interact with is the one belonging to the view controller at the top of your hierarchy which controls the whole screen.

Important: Although you add a subview by calling addSubview on the parent view, you remove a subview by calling removeFromSuperview from within the subview – i.e. you ask the subview to remove itself.

The CGFloat structure is used for all view coordinates.

Common structures used for views are:

  • CGPoint  – created with CGPointMake
  • CGSize  – created with CGSizeMake
  • CGRect  – created with CGRectMake

View coordinates and sizes are measured in “points” not pixels. To be future-proof, try to avoid any reference to pixel sizes.

There is a UIView @property CGFloat contentScaleFactor which returns pixels per point on the screen the view is on. This can be used when you really need to know the scaling factor e.g. when drawing charts etc.

Views have 3 important properties related to their location and size:

  • @property CGRect bounds; // Your view’s internal drawing space’s origin and size. Normally you always use bounds coordinates when drawing within your view.
  • @property CGPoint center; // The American centre of your view in it’s superview’s coordinate space.
  • @property CGRect frame; // A rectangle in the superview’s coordinate space that entirely contains your view’s bounds.size.

Use center and frame to position your view within it’s superview (see slide)

Slide from Stanford Lectures Fall 2001To calculate the centre point of a view in bounds coordinate space use:

= (bounds.size.width/2+bounds.origin.x, bounds.size.height/2+bounds.origin.y)

The designated initializer for UIView is initWithFrame:

self.view is always a Controller’s top-level view (i.e. the first subview in the self.subviews array).

One common reason to create a custom view (a UIView subclass) is to handle touch events in a special way.

To draw in a UIView, just override one method:

- (void)drawRect:(GCRect)aRect;

You can optimize by not drawing outside aRect if you want – but this isn’t a requirement.

NEVER, EVER call drawRect: !! – drawing is expensive! Let iOS decide when to do it!!!

Instead, let iOS know that your view’s visual’s need updating with one of these UIView methods:

  • – (void)setNeedsDisplay:
  • – (void)setNeedsDisplayInRect:(CGRect)aRect;

iOS will then call drawRect: for you at an appropriate time.

Like ye olde Windows 3.0 programming days, you draw into a “context“. The context determines where your drawing goes e.g. screen, off-screen bitmap, PDF, printer etc.

drawRect sets up the current context for you – a new one is set up each time drawRect is called – so never cache the current context in drawRect: to user later!

You get the current context within drawRect: by calling:

CGContextRef context = UIGraphicsGetCurrentContext();

This is usually your first line within drawRect:

Drawing…

  1. Get the context.
  2. Define a Path.
  3. Set the graphics stroke and fill
  4. Call CGContextDrawPath
Drawing

Drawing

Note that you don’t specify the context for setFill & setStroke – they assume the current context (i.e. the one obtained with UIGraphicsGetCurrentContext())

You can also save and reuse paths using similar functions to the CGContext… ones but prefixed CGPath… This is useful if, for example, you wanted to define a star and then display it 100 times.

UIColor is a class. Methods exist for obtaining common colours e.g. [UIColor redColor]

Call UIColor with setFill and setStroke to return fill and stroke styles in a particular colour.

In iOS, alpha values range from 0.0 (transparent) to 1.0 (opaque).

UIView has a backgroundColor @property which can be set to transparent values.

You have to set UIView @property opaque to NO if you want to use transparency. By default it is YES for performance reasons.

The UIView @property alpha can be used to make the entire view support transparency.

The UIView @property hidden allows you to hide a view. When hidden, it will not be shown on screen and will not handle events. This is a great way to de-clutter a screen by hiding a temporarily unused view. I suspect this is a good performance tip too since it doesn’t handle events when hidden.

Pushing and Popping contexts…

Use UIGraphicsPushContext and UIGraphicsPopContext to preserve a context’s state e.g. if you’re calling a sub-routine which may effect it. See slide.

Preserving Graphics State

Preserving Graphics State. (© Standford University)

Drawing Text in a Custom Font…

You can simply use a UILabel to display text. You can get very fancy using this! However, if you really want to have full control of your font, you can use the UIFont object. See the slide. Perhaps surprisingly, NSString is used to display text in your custom font. These NSString methods are actually defined in UIKit using a mechanism called categories.

Drawing Text

Drawing Text. © Standford University.

Drawing Images…

This refers to bitmap images. Commonly you’d use a UIImageView to display an image (or animation). But this is not the only way. You can even create a bitmap image by drawing with the CGContext functions. See the slides , below…Drawing Images - Slide 1Drawing Images - Slide 2

Using and Abusing UIViewControllers

Jonah Williams has some life changing advice on the proper and improper use of UIViewControllers in his post here. This post will switch on the light bulb for anyone coming to iOS development from another platform such as .NET.

This is his (and Apple’s) advice In a nutshell:

  • One (and only one) view controller should be responsible for a whole hierarchy (or screenful) of UIViews.
  • Mostly, you should only use one view controller per screen. Essentially the rootViewController of the current UIWindow should be the only UIViewController with a visible view.
  • Each different screen should have a different view controller i.e. one controller should not control more than one screen.
  • You should NOT nest custom UIViewControllers within a view hierarchy.
  • If more than one UIViewController hangs off the application’s UIWindow, only one of these will get the messages for changes in orientation. The other(s) will NOT get these messages.
  • Nested UIViewControllers are not guaranteed, or likely, to receive messages for changes in orientation or lifecycle messages such as viewDidAppear:, viewWillAppear:, viewDidDisappear: and viewWillDisappear: even though they inherit from UIViewController. Only the topmost UIViewController is certain to get these messages.

Some interesting discussions on this point on StackOverFlow:

iPhone viewWillAppear not firing

Am I abusing UIViewController Subclassing? – Nice quote from this:

Each screenfull should have one master VC Subclass, with all the subviews controlled instead by custom controllers (which happen to control views) which are subclasses of simple NSObject.

In this case, UIViewControllers should only be directly to the Window or UINavigationController, UITabBarController etc?

Also, this quote from Apple’s View Controller Programming Guide:

View controllers are directly associated with a single view object but that object is often just the root view of a much larger view hierarchy that is also managed by the view controller. The view controller acts as the central coordinating agent for the view hierarchy, handling exchanges between its views and any relevant controller or data objects. A single view controller typically manages the views associated with a single screen’s worth of content, although in iPad applications this may not always be the case.

With iOS 5 and the introduction of Custom Container View Controllers, much of this will change (see Pat Zearfoss’s comment, below). But it is important to understand the above as a starting point.