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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s