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

Great tip: viewDidLoad: is always called AFTER all outlets are wired up when a view controller is instantiated from a storyboard. Therefore, this is a good place to shove any initialization code that need to reference outlets.

UIToolBar

  • Usually at the top or bottom of your view.
  • A collection (NSArray) of UIBarButtonItems easily accessed read and written using self.toolbar.items.
  • Has a default “steel” effect UI, but can be customized using barStyle, backgroundColor or a background image etc.Fig 1
  • The UINavigationController has an optional toolbar at the bottom – its toolbarHidden @property is set to YES by default but you can switch it on by setting this to NO programmatically or via a toggle button in the Storyboard.
  • Set the toolbar.items @property in each embedded view controller to an array of UIBarButtonItems to control what buttons appear for that view controller.
  • A UIBarButtonItem can show text or an image, or you can even set it to a custom view.

UISplitViewController

  • Only available on the iPad.
  • A UISplitViewController will not even be presented as an option for an iPhone storyboard.
  • A storyboard can be either iPad or iPhone specific. You must specify which when you create it.
  • A split view is generally a “root” UI element only – i.e. it fills a whole screen.
  • It is usually the initial view controller and not normally embedded in another view controller.
  • Usually you would only have one split view controller in your app.
  • The master and detail view controllers are specified in an NSArray accessible via the viewControllers @property in the split view controller. The master view controller is element 0 and the detail view controller is element 1.
  • Usually you’d just specify these using drag and drop in your storyboard.
  • Note that the viewControllers array is passed as a “copy” @property, not a strong reference, so you can’t pass in a mutable NSArray then change its elements later and expect the split view controller to change.
  • The UISplitViewController requires a delegate @property for use in portrait mode or, by default, there will be no way to display the hidden master view controller.
  • Usually you set the delegate in viewDidLoad or awakeFromNib.
  • Depending on the implementation, either the master or detail view controller may be the delegate. More often, it is the master since it is often always present in memory (even when not displayed).
  • The delegate controls in which screen orientations the master view controller appears or is hidden. The detail view controller (normally to the right) is always visible.
  • The delegate is declared as:
    @property (nonatomic, assign) id <UISplitViewControllerDelegate> delegate;
    So beware, assign is like a “weak” pointer but without the “zeroing” action – so in theory, you could get a dangling pointer, but this is unlikely within a split view controller.
  • The delegate is asked about when the master should be on screen. When the master is about to be removed from the screen, your delegate will be informed and passed a UIBarButtonItem which you need to display on screen in your detail view controller. When selected by the user this button will display the master (usually in a popover).
  • When the master view controller is about to be hidden from view, your delegate will again be informed and be responsible for removing the UIBarButtonItem.
  • The screen shot below shows the delegate message and demonstrates how you can instruct the split view controller when to hide/show the master view controller.
  • Fig 2Note how you can specify that the master (left) view controller is ALWAYS visible by returning NO for every orientation:Fig 3
  • The default is to hide the master in portrait mode:
    return UIInterfaceOrientationIsPortrait(orientation);
    This is why you always need a delegate or you’ll never see the master view in portrait mode:Fig 4
  • You handle the following message in your delegate to show the bar button – see how iOS kindly passes the UIBarButtonItem to use! easy!Fig 5
  • When the master view controller is about to be shown, you handle the opposite message and remove the bar button:Fig 6
  • Here’s a typical implementation for the setSplitViewBarButtonItem: method. This will show the button when passed, and hide it if the passed button is nil:Fig 7
  • You could handle this in the master or detail view controller – it depends which one you set as your delegate. Normally, you’d chose the view controller that’s gonna be in the split view controller all the time (even when it’s not visible), or you might chose the least generic controller. Obviously, the button is always shown in the detail view.
  • The simplest way to update the detail view is by using a target/action.
  • Note we can pass messages to the detail view controller using:
    id detailVC = [[self.splitViewController viewControllers] lastObject];
    See slide…Fig 8
  • …or, you can use a “Replace” segue. This is actually more hassle though. Note the warning in the above slide. You’d normally pass the UIBarButtonItem in prepareForSegue.

Popovers

  • A Popover is not a UIViewController – it just inherits from boring old NSObject.
  • Its content is provided by an embedded “Content” view controller which you supply.
  • You can animate a change of content in a Popover already on screen with:
    – (void) setContentViewController: (UIViewController *) viewController animated: (BOOL) animated;
  • You can also animate a change in size using:
    – (void) setPopoverContentSize:(CGSize) size animated:(BOOL) animated;
    This can look sweet.
  • in prepareForSegue, the segue argument will be isKindOfClass:UIStoryboardPopoverSegue which has a popoverController @property which you can use to grab a reference to the Popover.
  • Tip: Detect whether a Popover is visible using its isPopoverVisible @property.
  • WARNING: You MUST keep a strong pointer to your Popover controller since when a local variable goes out of scope your Popover will be deallocated! This is a common mistake!
  • You can add views to a Popover’s NSArray *passthroughViews @property if you want to ensure your Popover is not dismissed by the user touching certain views outside the Popover.
  • If you want, you can add the whole screen to passthroughViews – in this case you absolutely need a button in the Popover content to dismiss it.
  • It’s best practice to always call dismissPopoverAnimated: from outside your Popover – probably triggered via a delegate message to the view controller that initially presented it – rather than from inside – even though the latter seems easier. This is good object-orientated practice since the Popover content view controller should not need to know it’s in a Popover!Fig 9
  • The best way to specify your Popover’s size is to set the contentSizeInPopover @property of your content view controller since this is most likely to know the size it “wants” to be and can also be calculated on the fly and set programmatically.Fig 10

Universal Applications

  • Use a single binary image with separate storyboards for the iPhone and iPad.
  • How do I figure out I’m running on an iPad?Fig 11
  • BUT – whenever possible, don’t do this! Instead, use “feature-sniffing” rather than forking based on the platform. For example, if self.splitViewController is not nil you must be running in a split View Controller on an iPad. Anyone coming from a JavaScript background will recognize this technique.
  • A handy tip to find out if a view is actually on screen is to check its window @property:Fig 12Other handy ways to get the size of the current screen and the resolution:Fig 13The contentScaleFactor will return pixels per point e.g. 2 on a retina display. It’s a good way to find out what kind of screen you’re running on.
  • By default a UIViewController doesn’t support landscape orientation. This is why, if you just drag a generic view controller onto a storyboard and add a segue to it, when it runs the view will appear in portrait mode regardless of the device’s orientation. To solve this you can create a generic, custom sub-class which inherits from UIViewController and merely overrides the shouldAutorotateToInterfaceOrientation: method. A nice name for this would be “RotatableViewController”. You can use this class for all these instances even in other applications.
  • You can easily copy and paste view controllers from one storyboard to another. This is a great way to create a new storyboard e.g. for the iPad based on an existing one for the iPhone.
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

Dynamic Message Binding in the Objective-C Runtime

Forgive the horrible title. I’ve just read one paragraph in the Apple document entitled “The Objective-C Programming Language” which explains how dynamic message binding, as implemented by the Objective-C runtime, is the seat of it’s power and versatility. Here’s that paragraph:

When a message is sent, a runtime messaging routine looks at the receiver and at the method named in the message. It locates the receiver’s implementation of a method matching the name, “calls” the method, and passes it a pointer to the receiver’s instance variables.

When you understand the consequences of this small paragraph and how it makes iOS messaging utterly different to static method binding, you’ll be well on your way to iOS enlightenment 😉

Objective-C class level “properties”. How to declare them and how to initialize them!

I wanted to have a private NSSet in my parent class to hold a collection of objects which could be searched by a class level method accepting a search item. I could easily have defined this NSSet as an instance @property and do lazy instantiation in its getter. But this would be inefficient as the set of objects would be identical in EVERY instance. But how to declare a class level “property” and initialize the members of my set?

Objective-C allows you to declare static variables at class level. Static variables only have one instance shared between all instances of your class. Private, static variables should be declared in your .m file within the @implementation – preferably just after @implementation for good code readability. The syntax is easy:

@implementation myClass

static NSSet *mySetOfObjects;

@end

But how do I initialize my NSSet? Here we use the magic initialize method:

+ (void)initialize {
    // Initialize your static variables here...
}

As you can see, initialize  is a class level method inherited from NSObject so therefore inherited by every class. The runtime sends initialize to each class in your program exactly one time – just before the class, or any class that inherits from it, is sent its first message from within your program. This is very efficient – kind of like lazy initialization for classes since if your class is never used initialize will never be invoked! Superclasses receive this message before their subclasses – this means there’s no need to invoke initialize on the superclass from within your code.

Putting this all together results in the following code, simple and efficient!:

@implementation MyClass

static NSSet *mySetOfObjects;

+ (void)initialize {
    mySetOfObjects = [[NSSet alloc] initWithObjects:@"one", @"two", @"three", nil];
}

+ (BOOL)isRecognizedString:(NSString *)searchItem {
    return [mySetOfObjects containsObject:searchItem];
}

@end

In this example I declare a class method which returns True if a string is contained within my set. The beauty is that every instance of this class will share the same code and the same static NSSet and the initialization of that set will happen only once and only if my class is actually used. Obviously this is just a simple example but these are the basics on which you can build.

Gotcha!

OK, when I said that initialize was called only once…it wasn’t the full truth. There is one caveat. Because of inheritance, an initialize message sent to a class that doesn’t implement the initialize method is forwarded to its superclass, even though the superclass will already have received the initialize message. For example, assume someone creates a class that inherits from our class but they don’t implement initialize in their class. When initialize is called on this subclass, because it isn’t handled, it will be called on its superclass – our class, instead. That means we need to insert a tiny bit of logic in the above code to guarantee that we only initialize our class once even in the above scenario. Don’t worry, just copy the method used in the code below.

@implementation MyClass

static NSSet *mySetOfObjects;

+ (void)initialize {
    if (self == [MyClass class]) {
        mySetOfObjects = [[NSSet alloc] initWithObjects:@"one", @"two", @"three", nil];
    }
}

+ (BOOL)isRecognizedString:(NSString *)searchItem {
    return [mySetOfObjects containsObject:searchItem];
}

@end

That if statement simply ensures that our initialization code only executes if ‘self’ is of class MyClass. If initialize is being called from a subclass then this will obviously be false, since the subclass must, by definition, be of a different type.

There, that final check will now ensure your code is rock solid 🙂

How @class helps resolve circular references in header files.

I just experienced my very first compile failure caused by circular references in Ojective-C header files. This is where Class X imports Class Y and Class Y imports Class X. John Muchow has a very nice explanation of how using the @class directive can resolve this.

Note that there also exists a @protocol directive which can be used in a similar way if you happen to create a circular reference when protocol A references protocol B and vice versa.

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

Protocols and Delegates – a really simple example

Thanks to John Muchow for this post explaining how to use protocols and delegates so that one object can notify other objects when an event happens in iOS.

There’s a great comment on this page by a “Grant” which encapsulates what we’re trying to do in a nutshell:

“The best way to describe what it does is a “call back”. i.e. Object A creates Object B and tasks it to do something. Object A would like to know when Object B is done, but rather than it keep asking Object B at set intervals: “Are you done yet? Are you done yet? Are you done yet? etc…” (polling), it instead just says to Object B, “This is a reference to me, use this reference to let me know when you’re done”.