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

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 3

If you implement the getter and setter for a @property yourself, there is no need to call @synthesize at all.

Its possible that some (usually read-only) @properties might be “calculated” rather than stored to a member variable.

C structures (quite common in iOS) are (almost) never allocated on the heap and so are not referenced by pointers. They are passed around by value. E.g. CGPoint:

typedef struct {
    float x;
    float y;
} CGPoint;

Like class names, it is standard practice to capitalize structure names (and enums) such as CGPoint.

Pointer Referencing

strong reference
– means “keep in the heap until I don’t point to it anymore”.

You won’t point to it anymore if:
You set your pointer to nil.
Or
Your pointer is removed from the heap because no one strongly points to it!

weak reference
– means “keep this as long as someone else points to it strongly“.
If your pointer is thrown out of the heap in iOS 5 or later, it is automatically set to nil so its safe and easy to check for this.

Unlike garbage collection (as used by other frameworks such as .NET) Automatic Reference Counting (ARC) IMMEDIATELY frees objects as soon as they become unreferenced.

Local variables are strong by default and get cleaned up by ARC as soon as they go out of scope.

-(void) dealloc: is called on your object when it is freed – but by this time its really too late to do anything useful. You’d rarely intercept it. dealloc has become less important since iOS 5.

Since the introduction of ARC with iOS 5, release, retain and autorelease are no longer used.

Class methods are normally used for creation and utility functions (e.g. alloc) or to get a reference to a shared instance. They are called with the name of the class or by obtaining a reference to the class by calling an instance with the “class” method.

[[ship class] myClassMethod];

Instance methods are normally called with a pointer to an instance.

Classes cannot have properties as properties are instance variables.

All objects must have “init” or some form of init called immediately after alloc. We ALWAYS nest these calls in one statement e.g. [[MyObj alloc] init];

All classes inherit init from NSObject. If a class cannot be fully initialized with a call to init the guidelines suggest that (strictly) you should code init to raise an exception.

Designated Initializers…

All your classes should declare a designated initializer for sub-classes to call (using [super init…]). Sub-classes MUST call this initializer from their designated initializer (using [super…]). E.g. a sub-class of UIView should ALWAYS call [super initWithFrame]. You should declare your designated initializer in your documentation, there’s no way to indicate this in code. Consult Apple’s documentation to find out the designated initializer for classes you wish to sub-class in the Objective-C framework.
Only call the super-class’s designated initializer from your designated initializer – no-where else. If you have other initializer’s in your class, make sure they call your designated initializer to guarantee the super class’s designated initializer is called regardless of which initializer is called to initialize your class (dig? – dug).

init methods should return an id (not a static type). The caller though should use a static type for the returned pointer.

An example of an initialization method

DYNAMIC BINDING – VERY IMPORTANT!!!

Objective C is different to most statically typed languages in that the code to send a message to an object method is generated at runtime NOT at compile time. This is very important to realize as it is intrinsic to the way an iOS app operates. Static typing in your source code is purely an aid to the compiler to help you find bugs at write time. Dynamic binding at runtime allows messages to be sent to any object at runtime. As long as the object supports that message this is fine – if not, your app will crash. you can use the “introspection” method respondsToSelector to test whether a receiving object supports a particular message.

(See “The Objective-C Runtime Programming Guide” for the technical details of messaging)

You can obtain a dynamic reference to a method (referred to as a SELECTOR) at runtime using various commands (use the SEL type to store a reference to a Selector)

Objective-C classes support Introspection using various methods e.g. isKindOfClass, isMemberOfClass, respondsToSelector etc. The “class” method mentioned earlier is handy here.

if ([obj isKindOfClass:[NSString class]]) {
    NSString *s = [(NSString *)obj stringByAppendingString:@"xyz"];
}

Introspection

The same NSArray can hold references to different object types. You cannot store nil in an NSArray though – use NSNull instead. NSNull acts as a placeholder indicating “no value”.

Contrary to what you’d assume, NSMutableString is rarely used. It doesn’t really save much heap thrashing compared to the immutable NSString class which is highly optimized for most operations.

NSStrings are mostly passed around as immutable copies.

Many objects support copy: and mutableCopy: to convert between mutable and immutable types.

NSValue can be used as a generic object wrapper even for C structures such as CGPoint.

CGPoint point = CGPointMake(25.0, 15.0);
NSValue *pointObject = [NSValue valueWithCGPoint:point];

This would let you store a bunch of points as objects in an NSArray.

NSData is just a “bag of bits” (similar to a “Blob” in other languages). Its up to the app to interpret what the bits mean (e.g. a jpeg image). NSData is commonly used to save/restore and transmit data of many kinds throughout iOS.

Tip: NSArray containsObject: can be slow. Think about using an NSOrderedSet instead. NSOrderedSet is an immutable, ordered (indexed) collection of distinct objects.
NSSet is similiar to NSOrderedSet but is unordered – i.e. has no index for referencing.
One of the most important features of NSSet and NSOrderedSet is that they are geared up for highly efficient searching using “contains”. Also, they will only store “distinct” objects. If you attempt to add an identical object (where isEqual == YES) the attempt will simply be ignored. These two features make them ideal for looking up a match for any object.

Enumerating through collections
Using for…in is highly efficient see slides below from the lecture for example syntax.

Enumeration - Slide 1

Enumeration - Slide 1

Enumeration - Slide 2

Enumeration - Slide 2

Property Lists (aka “plist”)

These seem to be just collections of collections where the collection members are “simple” types – i.e. not your own, custom classes.

Property List

Property List (Slide from Lecture 3)

NSUserDefaults makes use of a plist for persisting data such as a user’s preferences.

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

We usually make an outlet @property weak since the view will already have claimed a strong reference to it, and, if the view exists, it can usually be assumed the view object exists e.g. a UILabel outlet.

Most “non-outlet” @properties are declared as strong for the opposite reason – i.e. only we are referencing them. Practically all controllers will have a strong reference to their model.

NSNumber acts as a handy, object wrapper around primitive number types (e.g. double) that we can pass to framework methods that usually require an object and for storage in NSArray, NSMutableArray, NSDictionary etc. e.g. [NSMutableArray addObject:];

Like NSNumber, you can pass NSString messages such as doubleValue, floatValueintegerValue etc. to convert string values to primitive, numeric types.

A view controller can have IBOutlets from more than one Storyboard e.g. you could have one Storyboard for the iPad and one for the iPhone in a universal application.

id does not mean “object of any class”, it means “pointer to an object of any class”. This is why we use (id) rather than (id *) when passing an id to a method etc. The “*” is implicit.

If an object needs allocating and/or initializing upon its first access its a neat idea to allocate and initialize it in its “getter”. This is refereed to as “lazy initialization”.

Remember: @synthesize NEVER allocates anything, it just sets aside a pointer to an object that can be instantiated.

All properties start out with a value of zero (0), or, if an object, nil.

If you send a message to a nil object the object will just do nothing and return zero (or nil) if the message expects a return value. This is allowed, your app will not crash! The only exception is if the method returns a C struct (e.g. CGPoint) in which case the return value will be undefined.

Use an isEqual method to compare objects, not == since == only compares the object addresses.

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

A @property setter’s name is “set” plus the capitalized @property name e.g. the setter for a @property named myValue would be setMyValue. The “getter” is simply: myValue.

It is considered best practice to always name properties starting with a lowercase letter e.g. myProperty not MyProperty.

You can still create a getter and setter even if you use @synthesize in your implementation file. If you do your getter or setter will be called instead of the synthesized one. This is common practice e.g. to implement “lazy initialization” in the getter.

Property and method declarations are considered public if declared in your header (.h) file and private if declared in your implementation (.m) file.

To declare private properties and methods in your implementation file, wrap them in the @interface … @end compiler directives as in your header file but put a parenthesis after the @interface() opener:


@interface CalculatorViewController()

// Private declarations...

@property (nonatomic, strong) CalculatorBrain *brain;

@end

To help the compiler and yourself, always specify the name of the hidden member for your @properties e.g. @synthesize myValue = _myValue; The compiler will then warn you if you omit the “self” keyword when referencing a member property.