Notifications

Notifications are messages that are sent between objects. Unlike delegates, which are specifically linked to the object sending the message, notifications are broadcast and intercepted without any link between the sender and receiver. In fact, there can even be multiple receivers listening for a notification. Think of it like a radio station, which broadcasts a program whether it has listeners or not, and like radio receivers which can tune in to receive that program.

The NSNotification object is used to build a message. A message contains three properties:

  • The name is the message identifier.
  • The object is the sender of the message.
  • The userInfo is an NSDictionary containing information about the message.

You can create an NSNotification using the notificationWithName:object:userInfo: class method. Chances are, however, that you’ll never create a notification directly, but do so through the notification center.

Notification Center

The NSNotificationCenter provides a mechanism for broadcasting information within a program. This would be the radio station in our analogy.

Each app has a default notification center you can use. You typically will never need to create your own. To access this notification center, you use the defaultCenter class method.

Listening for Notifications

Any object can register with a notification center to receive NSNotifications using the addObserver:selector:name:object: method. The object parameter is used to set a specific notification sender, but you can also pass nil to register for all notifications identified by the name parameter.

An object can also register for a notification using a block handler. This is done with the addObserverForName:object:queue:usingBlock: method. The queue parameter is used in multithreading environments, but since we haven’t looked at that yet, you can just pass nil to use the main thread.

You use removeObserver:name:object: to stop listening for a notification.

You can also call removeObserver: to stop listening for all notifications.

Posting Notifications

Posting notifications is also done through the NSNotificationCenter, using either the postNotificationName:object: or postNotificationName:object:userInfo: methods, depending on whether or not you need to pass info along.

Displaying Web Content

UIWebView is used to display web content. UIWebView is simply an extension of UIView with URL loading and displaying capabilities, so it can be used wherever you would use a UIView. UIWebView is built using the WebKit framework, which is the same set of libraries used in Safari, making it surprisingly fast.

You can add a web view directly in the Storyboard. You can also set whether or not the web view scales pages to fit on the device screen. This can also be done in code by setting the scalesPagesToFit property.

Web pages are loaded using NSURLRequest.

Header Source

UIWebView has some built-in navigation methods you can call to control page display:

  • goBack loads the previous page in the stack. canGoBack determines if we are at the bottom of the stack.
  • goForward loads the next page in the stack. canGoForward determines if we are at the top of the stack.
  • reload reloads the current page.
  • stopLoading stops loading whatever content it’s loading.

These actions can be linked directly in the Storyboard.

The web view delegate can receive information on when page loads starts and ends. It must conform to the UIWebViewDelegate protocol. We can use this information to update button states in our user interface.

Header Source

You can also implement webView:didFailLoadWithError: to handle any errors.

You can add a UIActivityIndicatorView to give more feedback to the user.

Header Source


 

Designing for iPad

All of the apps we have built so far can run on an iPad. We can simply install the same apps on an iPad device and they will work the same as on an iPhone. Because of the difference in screen size and resolution, iPhone apps will run in a window on an iPad.

A 2x button in the bottom right of the screen will scale up the app for a better fit on the iPad screen, but that tends not to look very nice as the graphics get pixelated.

It is important to take the iPad’s screen size into consideration when developing apps in order to provide a better user experience. More space and a different aspect ratio means that you can put more information on screen and have a different layout.

The starting point to building apps for iPad is simple; You just select the iPad device from the drop-down when creating a new project in Xcode.

Split Navigation

iPad projects have the possibility of using the UISplitViewController for navigation, which can present two view controllers side-by-side. This type of layout is commonly used for Master-Detail relationships, like for example a table view and a detail view populated by a selected cell.

Much like the UITabBarController, the UISplitViewController is a base view controller and should only be used as the initial view controller of your app.

The simplest way to build a split view controller layout is to start with a Master-Detail Application for iPad.

If you examine the Storyboard, you’ll notice that the initial view controller is the UISplitViewController, and that it is connected to two UINavigationControllers, one for the Master relationship (on the left), and the other for the Detail relationship (on the right). The master navigation controller is connected to a UITableViewController, and the detail navigation controller is connected to a simple UIViewController.

The master and the detail can be accessed from the split view controller’s viewControllers array. This array holds only two elements, the first being the master and the last being the detail.

Both the master and detail view controllers have references to their parent split view controller that is automatically set in their splitViewController property. This property is used to link the master view controller to the detail view controller.

[self.splitViewController.viewControllers lastObject] references the detail navigation controller. The topViewController of this navigation controller is the detail view controller.

You can use this reference to pass information to the detail view controller.

 

If you run the app, you’ll notice that both views are visible in landscape orientation.

However, in portrait orientation, the master is hidden by default.

It slides in from the left, partly covering the detail, when the Master button in the navigation bar is tapped.

You’ll also notice that the Master button is only visible when the device is in portrait orientation. This does not happen automatically, but through delegation.

The detail view controller conforms to the UISplitViewControllerDelegate protocol and is set to be the split view controller’s delegate.

When the split view controller switches to portrait orientation, it calls its delegate’s splitViewController:willHideViewController:withBarButtonItem:forPopoverController: method. The third parameter to this method is the UIBarButtonItem that will present the master view controller when tapped. The delegate can therefore add this button to the navigation bar.

When the split view controller switches to landscape orientation, it calls its delegate’s splitViewController:willShowViewController:invalidatingBarButtonItem: method. The third parameter is the UIBarButtonItem that is not necessary anymore, and should be removed from the navigation bar.

If you always want the master view controller to be displayed, the delegate must implement splitViewController:shouldHideViewController:inOrientation: and return NO. Note that by doing so, splitViewController:willHideViewController:withBarButtonItem:forPopoverController: and splitViewController:willShowViewController:invalidatingBarButtonItem: do not get called anymore.

Popovers

The UIPopoverController is used to display a view controller temporarily. Unlike a modal view controller, a popover does not take over the entire screen, but is layered on top of the main view in a special type of window.

The popover controller’s content is a UIViewController, which can be accessed using the contentViewController property. You can easily create a popover in the Storyboard by creating a Popover style segue between any two view controllers.

You can also create it in code.

Header Source

Note that if you are also responsible for dismissing the popover programmatically. You can use the popoverVisible property to check whether the popover is displayed or not.

The easiest way to set a popover controller’s size is by setting the view size of its contentViewController, but you can also set it in code using setPopoverContentSize:animated:.

The UIPopoverControllerDelegate can be notified when a popover is closed by implementing the popoverControllerDidDismissPopover: method.

Modals

Modal views are presented on iPad in the same way as on iPhone. However, it will sometimes not look good for a presented view to take up the entire screen. You can set the modalPresentationStyle property on the presented UIViewController for a different style.

Possible styles are:

  • UIModalPresentationFullScreen – The default, like on iPhone.
  • UIModalPresentationPageSheet – Full height, but always in portrait size (even if the view is in landscape).
  • UIModalPresentationFormSheet – Centered in the view, with everything around dimmed.
  • UIModalPresentationCurrentContext – Same as the parent view controller.

Universal Applications

Universal applications are apps that run on both iPhone and iPad.

To create a Universal app, you just select the Universal device from the drop-down when creating a new project in Xcode.

This will generate two Storyboards: MainStoryboard_iPad.storyboard and MainStoryboard_iPhone.storyboard. This is where following the MVC pattern comes in handy. Each Storyboard is a different View, and they use the same Model (data). If both Views have similar feature sets, they will probably use the same Controller too.

In your view controller, you can detect which type of device the code is running on by checking the userInterfaceIdiom property of the current UIDevice.


  1. Paul Hegarty. Lecture 7 Slides. iPad and iPhone Application Development. Stanford, Nov 16 2011.

  2. View Controller Programming Guide for iOS. Apple, Visited Oct 23 2012.

Scrolling Forms

Sometimes, you will need to build forms to capture a lot of information from your user. Download and run the following starter project.

If you don’t put too much thought into this, it can be a real pain for your user. Let’s look at ways to make this process as easy as possible.

Customizing the keyboard

The first thing to do would be to set the right keyboard type for each field. In the above example, most fields will just use the Default keyboard, but you should set the email field to use the E-mail Address keyboard, and the phone number field to use the Numbers and Punctuation keyboard. This will prevent the user from manually having to switch keyboards when inputting that data.

Another easy improvement is to set an appropriate title for the Return key. In our case, it should be Next for all fields but the last one, where it should be Done.

Auto-selecting the next field

The next improvement would be to hide the keyboard when the Return key is pressed. In your past apps, you probably did this by calling resignFirstResponder on your UITextField instances, either as a callback from the delegate or from the Did End On Exit event.

In this case however, it would be better to automatically select the next field until we reach the last one, where we would finally hide the keyboard.

First, set your view controller to conform to the UITextFieldDelegate protocol.

Then, set the delegate of each text field to your view controller.

Then, set the tag of each text field to an incrementing value starting at 1. We start at 1 because the default is 0, and we want to filter out all other UIViews on screen. In this example, the first name text field will have tag 1, the last name will have tag 2, and so on until the phone number at tag 9.

Once that’s done, you can implement textFieldShouldReturn:, which will get called every time the Return key is pressed.

The code will read the tag of the field that just returned. If it’s the last one, it will hide the keyboard, otherwise it will automatically focus on the next field.

You can go one step further and automatically populate the list of entry fields. That way, you can add or remove fields without having to worry about the value of the last tag.

Scrolling the fields into view

You’ve probably noticed by now that all the fields in the bottom half of the screen are hidden by the keyboard. This is really bad for the user experience because you can’t tell if you’re in the right field or even if you’re entering any data at all.

The solution to this problem is to put all the fields in a scroll view, and to have it automatically scroll so that the field in focus is always visible.

The first thing you need to do is to calculate how much space the keyboard takes up. You can do this by listening for notifications on when the keyboard will show, and to measure its size when it is on screen.

You can then move all the text fields inside of a UIScrollView, and create an outlet for this scroll view in your view controller.

Header Source

You can then add a method that will scroll to position a subview in the middle of the available space on screen, and call that method every time a new text field is selected.

At first glance, this seems to work fine, but if you play with it a bit you’ll notice that the centering code does not work the first time it is called. That’s because textFieldDidBeginEditing: is called before keyboardWillShow:, and so the _keyboardBounds are not set the first time scrollViewToCenterOfScreen: is called.

You can fix this by saving a reference to the text field that is being edited, and calling scrollViewToCenterOfScreen: directly from keyboardWillShow:.

Finally, you’ll want to add a method to scroll the view back down to its original position, and call that method whenever the keyboard will hide.

You might notice that the view doesn’t actually animate down, but jumps down directly. That’s because of the call to setContentSize:, which sets the content to fit into the app frame, so there’s nowhere left for it to scroll. This can be fixed by “manually” animating the offset.