Structure Your App
To implement your software, you make design decisions based on how you’d like to structure your app in terms of organizing your code. To decide your iOS app structure up front, the top-down approach and model-view-controller (MVC) design pattern are recommended and actually embedded in the iOS software development kit (SDK) and tools. MVC may not be enforced in most of the popular web development platforms, but the vocabularies should be familiar to both front-end and back-end web developers. The techniques may vary, but the principle is the same: structure your app into three software layers consisting of the model, view, and controller.
In the iOS programming paradigm, you are encouraged to take the so-called top-down approach for creating your mobile apps, where you design the application workflow prior to detailing each individual screen. Xcode will guide you to turn on your top-down programming thinking hat.
I will discuss MVC first, followed by how to create the iOS storyboard in Xcode. With the guided screen navigation patterns, your iOS storyboard naturally breaks your iOS apps into MVC components to form the skeleton of the iOS mobile app.
WEB DEV ANALOGY
Perhaps it is best not to try to compare the iOS MVC design pattern with any web MVC design patterns. For those who have worked in web development for a long time, you probably have seen many MVC framework variants. All you need to know now is there is only one MVC in iOS, and it is very clean compared to the various web MVC framework libraries.
MVC breaks the graphical user interface (GUI) app into three layers. The iOS MVC design pattern specifies that a GUI application consists of a data model, a presentation view, and controller layers, as shown in Figure 3-1.
Figure 3-1. The iOS MVC design pattern
In the iOS SDK, you explicitly use the MVC vocabularies of Content View and Content View Controller. You naturally break down your iOS app into MVC classes, starting with creating a storyboard prototype using the Xcode storyboard editor.
Unless your app has only one screen, you need to decide how to implement navigation and screen transitions among multiple view controllers. You need an optional MVC participant: a Container View Controller to coordinate the view controllers. In iOS, the SDK provides several Container View Controllers for screen navigation purposes; you simply choose the appropriate Container View Controller class and let the iOS framework facilitate the tasks for you.
I’ll start with the Content View and Content View Controller and then talk about the Container View Controller.
Content View
WEB ANALOGY
The Content View is equivalent to the web presentation layer. It could be a static HTML page, server-side template, or client-side template. The technique may be different, but the principle is the same: you create a presentation layer that is not tightly coupled to the data.
A Content View provides a visible area so that users can interact with the app. The Content View defines how to render itself with content and can interact with user actions. To create Content Views in iOS, use an Xcode storyboard.
When it comes down to implementing a Content View, you face the same issue you normally encounter when implementing a web page: how to make the page rendering adaptive to screen size. This is actually a hot topic in web development as well. Recall that in the iOS HelloMobile app, you drew the UI widgets in the storyboard scene by dragging and dropping the widgets to draw them on the parent view. However, this iOS Content View is not adaptive to other device types and screen orientations yet. You can easily observe the landscape problem in the Assistant Editor previews (for example, see Figure 2-14 in Chapter 2). Creating adaptive Content Views for various screen sizes is a common task in many platforms. In iOS, you essentially use iOS platform features for the same purposes.
Auto Layout
You use iOS Auto Layout to position each UI widget by aligning or spacing it relative to other widgets, that is, siblings or the parent view.
The three widgets in the current iOS HelloMobile project are not positioned properly in landscape mode (see Figure 2-14 in Chapter 2). Here you’ll use iOS Auto Layout to fix this while learning its uses.
While extremely powerful, some Xcode editors and operations are collapsed in the menus and can be difficult to find for a beginner. Figure 3-2 depicts some quick tips.
Figure 3-2. Storyboard Auto Layout operations in Xcode
Continue working on the HelloMobile project. Do the following:
Figure 3-3. Two steps to reach the Xcode storyboard preview
Figure 3-4. Using Horizontal Center in Container on the TextField in the Auto Layout editor
Figure 3-5. Creating a y-alignment constraint using a multiplier
Note Use Multiplier or Constant to create an offset for the second item position:
(first item position) == (second item) * multiplier + constant
Figure 3-6. Using Update Frames to reposition the UI widgets based on constraints
Figure 3-7. Spacing Hello World! label relative to its neighbors
Note The combined constraints need to make sense without ambiguity. You can set a priority for each individual constraint. However, if you start using priorities to resolve conflicts, you might want to think about using fewer constraints.
Using Auto Layout with responsive UX designs immediately provides the proper landscape layout. Figure 3-8 shows the previews in both landscape and portrait modes for iPhone 4-inch and 3.5-inch modes. Click the + icon (see the pointer in Figure 3-8) to add multiple previews for different devices.
Figure 3-8. Auto Layout with responsive UX design
Size Classes
WEB ANALOGY
Responsive web design would use media query breakpoints in the style sheet to target specific screen sizes.
While Auto Layout provides an effective way to implement responsive UX for various screen sizes, it may not utilize the valuable mobile-screen real estate in the most efficient way. For example, it is fairly common to lay out landscape view differently from portrait because of the different aspect ratios to have tablet-specific UX design, and so forth.
Prior to iOS 8, you generally implemented two storyboards, one for iPhone and one for iPad. Beginning with iOS 8, size classes were introduced to solve this common programming issue by using the abstract presentations of device sizes in terms of horizontal widths and vertical heights. The current iOS devices can be classified as shown in Table 3-1.
Table 3-1. iOS Device Size Classes
Size Classes |
Compact Width • iPhone: portrait width • iPhone: landscape width |
Regular Width • iPad: portrait width • iPad: landscape width |
---|---|---|
Compact Height: • iPhone: landscape height |
iPhone in landscape |
Customized view controller |
Regular Height: • iPad: portrait height • iPad: landscape height • iPhone: portrait height |
iPhone in portrait |
iPad in portrait iPad in landscape |
You can provide the entire implementation for all size classes all in one storyboard!
Recall the iOS HelloMobile app—it works only in iPhone portrait mode (see Figure 2-14 in Chapter 2), and it disables size classes. In the following steps, you will enable size classes to see how to use them:
Figure 3-9. Selecting Use Size Classes
Figure 3-10. Size classes preview
Figure 3-11. Using the size class selector to select a specific size class
Figure 3-12. Compact Width | Compact Height for iPhones in landscape
Figure 3-13. Two-sided view for compact height (iPhone landscape mode)
Figure 3-14. Updating the Auto Layout constraint
All the device classes in previews look good, as expected (Figure 3-15).
Figure 3-15. Device classes previewed in the storyboard editor
You can run the app in all emulators to see the work in action. Figure 3-16 shows the iPhone 4s emulator. The framework takes care the transition animations for you, too!
Figure 3-16. iPhone 4s portrait and landscape size classes
Content View Controller
WEB ANALOGY
This is the code that is responsible for getting data and injecting it into the template or Document Object Model (DOM) elements.
The Content View Controller participant pairs with a Content View (see Figure 3-1). In the iOS, the Content View normally is created statically in the storyboard. The Content View Controller class manages the Content View to present the dynamic behavior of the user interface by conveying information to and interacting with users. In iOS, you create a custom class by subclassing from UIViewController.
Your primary Content View Controller tasks are as follows:
In iOS, you normally use the storyboard editor to connect the UI widgets or events to your code to facilitate these common Content View Controller programming tasks.
Pair with Content View
In iOS, you normally create a storyboard for your apps first (like the iOS HelloMobile project). Generally, for every storyboard scene (Content View), you create a Swift class subclassed from UIViewController to pair with it.
The iOS HelloMobile project is not completed yet; it renders only the initial screen but does not do anything when you click the Hello... button. You need a functional Content View Controller that can fulfill this responsibility. The Single View Application template already pairs a controller class for you: ViewController.swift. To demonstrate the whole subject, don’t use this class; instead, do the following to create your own class:
Listing 3-1. HelloViewController Class Skeleton
import UIKit
class HelloViewController: UIViewController {
// TODO
}
Figure 3-17. Identity Inspector to pair with the view controller
Specifying the custom class in the Identity Inspector is all you need to pair with the Content View Controller.
Interact with Content View
WEB ANALOGY
To interact with an element in a web app, you would attach an event listener, such as onClick(), which would call a function in your controller code.
Generally speaking, you create UI widgets in a Content View, and your Content View Controller code updates the widget’s states or interacts with users at runtime. In iOS, you use the Connections Inspector to create IBOutlet and IBAction to facilitate this common programming task by drawing connections to your code in the Swift class.
The following walks you through the steps to connect the UI widgets and delegates action events to your controller class:
Figure 3-18. Selecting a file manually in the Assistant Editor
Figure 3-19. Opening the Connections Inspector
Figure 3-20. IBOutlet in the Connections Inspector
Figure 3-21. Creating an IBAction in the Connections Inspector
Listing 3-2. HelloViewController with IBOutlet and IBAction
import UIKit
class HelloViewController: UIViewController {
@IBOutlet weak var mTextField: UITextField!
@IBOutlet weak var mLabel: UILabel!
@IBOutlet weak var mButton: UIButton!
@IBAction func onButtonTouchDown(sender: AnyObject) {
var str = mTextField.text
mLabel.text = "Hello (str)!"
}
}
This completes the whole HelloMobile iOS app. You can run the project in all iOS emulators to see the code in action.
You’re almost done with the MVC topics. Just one more small lecture before getting into more fun stuff: UIViewController life-cycle events.
UIViewController Life Cycle
WEB ANALOGY
DOM life-cycle events such as DOMContentLoaded, window.onload, or callbacks using jQuery.ready() are typical for web applications that need to manipulate the DOM. To detect when a page is being closed, the window.onbeforeunload event is used.
In iOS apps, life-cycle callbacks are called at various points when a Content View is being rendered. Certain tasks need to be performed in certain states to ensure the Content View is rendered smoothly. This applies to many modem UI platforms including iOS and Android. These events may not be a beginner topic, but the purpose is simple: you want to perform certain computing tasks at the right time to ensure the rendering process is smooth and data is ready at the right time.
Most of these events are optional to implement; you can choose to override these inherited system methods to receive timely callbacks if you want.
The iOS system calls the viewDidLoad() method when the view controller loads its Content View. You commonly put the initialization code here.
viewWillAppear
The iOS system calls the viewWillAppear() method when the view is about to appear. Compared to vewDidLoad(), this method gets called every time you revisit the view, while viewDidLoad() gets called only the first time.
viewDidAppear
The iOS system calls the viewDidAppear() method when the view becomes visible. If you want to show rendering effects upon the view being visited, that is, animation effects, you want to put the code in this method. On the other hand, you want to avoid time-consuming code in this method to avoid the UI not being responsive.
The system calls the viewWillDisappear() method when the Content View is about to become invisible—for example, leaving for another storyboard scene. This is usually where you should commit any changes that should be persisted beyond the current user session (because the user might not come back).
viewDidDisappear( )
The system calls the viewDidDisappear() method when the Content View is not visible.
When implementing these life-cycle events, you almost always want to call the corresponding super.viewXXX().
Screen Navigation Patterns
You commonly need multiple screens to convey hierarchical information and to interact with users. Considering the relatively small mobile screens, it is more crucial that you use well-known navigation patterns to make mobile apps more predictable. A consistent and predictable navigation pattern guides users to complete a task with multiple screens. Efficient navigation is one of the cornerstones of a well-designed mobile app.
Storyboard Segue
A segue (pronounced “seg-way”) is a type of a connection in a storyboard that specifies transitions from one scene to another. For instance, you can create an action segue that is performed immediately when the action is triggered. More frequently, you will create a manual segue in storyboard and write logic to perform the segue. Depending on its transition type, the segue may require a Container View Controller. For example, to implement the typical navigation stack transitions, you will need a Navigation Controller in iOS.
The following steps will walk you through creating a storyboard segue:
Figure 3-22. Segue preparation
Figure 3-23. Creating an action segue
Figure 3-24. Selecting the segue and setting up the attributes
Figure 3-25. Creating a manual segue
Listing 3-3. Performing the Manual Segue
import UIKit
class ViewController: UIViewController {
@IBAction func onManualSegueTouchDown(sender: AnyObject) {
self.performSegueWithIdentifier("manualSegue", sender: sender)
}
}
Run the app in different emulators to see these segues work in different size classes. Since iOS 8, the segues are presented in an adaptive manner to the size classes.
Pass Data with a Segue
WEB ANALOGY
Typically, web apps will send requests to the server by passing data through the URL query string, form posting, or cookies. This loads the entire page content for each user action. Single-page front-end apps can utilize animated screen transitions through asynchronous data loading or by front loading more data during their initialization.
The storyboard segues perform screen transitions easily by drawing the segue connections. You don’t even need a line of code for an action segue. However, you normally will need to pass data from the presenting view controller to the presented view controller, which cannot be done alone by the storyboard segue itself.
The following steps demonstrate the conventional iOS way to pass data from the presenting view controller to the presented view controller in the Xcode Segues project:
Listing 3-4. Data Property to Receive Data from Presenting View Controller
import UIKit
class PresentedViewController: UIViewController {
var data: String?
override func viewDidLoad() {
if let tmp = data {
println("received data: (tmp)")
}
}
...
}
Listing 3-5. Presenting View Controller Override prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
var identifier = segue.identifier
var destVc = (segue.destinationViewController as PresentedViewController)
destVc.data = "some data from presenting vc (identifier)"
}
Container View Controller
In iOS, screen navigations are primarily implemented by storyboard segues and the Container View Controller classes from the iOS SDK that facilitate the screen navigations. You may create subclasses from these system Container View Controllers, but you normally just use them as is in most of the cases.
Note Generally speaking, the navigation patterns in this book should appear more familiar to mobile web developers with experience in responsive design or using a mobile framework. For traditional web developers, it is a small investment to get familiar with mobile web development. In addition to HTML5/CSS3, the jQueryMobile and Sencha Touch frameworks are both good choices to leap into this space.
Navigation Stack
The navigation stack is widely used to manage screen transitions, particularly for displaying an information hierarchy, such as a master drill-down list. To show the next screen, push the next view controller into the navigation stack. To go back to the previous screen, pop out the previous view controller from the navigation stack.
You will be guided to create a simple iOS app yourself so you know how to create it and can visualize how it works (see Figure 3-26).
Figure 3-26. Preview of the iOS NavigationStack app with three screens
In iOS, you draw appropriate storyboard segues with the UINavigationController Container View Controller to accomplish this navigation stack pattern. Let’s create a new Xcode project to demonstrate the iOS way.
Figure 3-27. Three storyboard scenes in the NavigationStack project
Figure 3-28. Screen One label with center constraints
Figure 3-29. Next button with alignment constraints
Figure 3-30. Three scenes with widgets in NavigationStack project
Figure 3-31 shows the results of the storyboard. Run the app to see what is working and what is not working yet. Nothing is new yet; these are the same steps that create storyboard scenes, UI widgets, Auto Layout constraints, and segues that connect them together.
Figure 3-31. NavigationStack storyboard
UINavigationController
The only missing piece in this iOS project is a way to show the appropriate child view controller in the typical push and pop fashions. In iOS, UINavigationController manages the push and pop screen navigation stack behaviors. It also provides a navigation bar that has a default Back button, a title in the center, and an optional right button (see Figure 3-32).
Figure 3-32. The UINavigationController navigation bar
All you need to do is to set up a UINavigationController that associates its root view controller with the first scene.
You can accomplish both in one simple storyboard operation: embed the Screen One view controller in a Navigation Controller, as shown in the following steps:
Figure 3-33. Creating a Navigation Controller
Figure 3-34. Navigation Controller scene and root view controller connection
Figure 3-35. Title on the Navigation Item
Note The title can be derived from the view controller title when the Navigation Item is not being configured.
Build and run the app to see the live app in action. You should see that your app looks like Figure 3-26 (shown previously).
Master List with Details Drill-Down
Many apps need to display a list of items that users can select to view more detailed information. They present a master list of items first, and the user selects one item to drill into.
This is probably one of the most common mobile navigation patterns. Most of the popular mobile frameworks including iOS, Android, jQuery mobile, and Sencha Touch mobile JavaScript libraries take care of the memory management for you using recycle views without rendering off-screen list items.
Android and iOS provide guidelines and offer system APIs to promote consistency by making the implementation easy for developers. Both Xcode and the Android IDE supply project templates for creating apps with this UX pattern.
You will create a simple iOS app (see Figure 3-36) to visualize how this works and how to create it.
Figure 3-36. Preview of the MasterDetail app screens in iOS
UITableViewController
As you can see in Figure 3-36, this app is purposely simple with two screens only. You need two storyboard scenes: one for the master list and the other for the detailed Content View. To make a fresh start, create a new iOS project.
Figure 3-37. TableViewCell attributes
Figure 3-38. MasterDetail storyboard
Listing 3-6. MasterListTableViewController Class
class MasterTableViewController : UITableViewController {
// TODO
}
Note In Swift, the class doesn’t need to be in its own file. I chose to put it in the existing ViewController.swift file for no particular reason.
UITableViewDataSource
To populate the items in the Table View, UITableView, you implement a data source. Specifically, you provide the TableViewCell with data by overriding the methods defined in the UITableViewDataSource protocol (see Listing 3-7). Do the following in the MasterDetail project:
Listing 3-7. Implement UITableViewDataSource Protocol
class MasterTableViewController : UITableViewController {
var items = ["item 1", "item 2", "item 3"]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("mycell") as UITableViewCell
cell.textLabel!.text = self.items[indexPath.row]
return cell
}
}
Note Use dequeueReusableCellWithIdentifier(...) to implement recycled views. This is a common pattern for saving memory with a large number of items, and iOS makes it easy by offering this method. Make sure you assign a cell identifier to the table view cell in the storyboard.
UITableViewDelegate
To handle table view item selected events, override the UITableViewDelegate.tableView(tableView, didSelectRowAtIndexPath) method (see Listing 3-8).
Listing 3-8. Implementing UITableViewDelegate
class MasterTableViewController : UITableViewController {
...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("detail", sender: indexPath.row)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject) {
var destVc = segue.destinationViewController as UIViewController
destVc.navigationItem.title = self.items[sender as Int]
}
Listing 3-9. Refresh Table View
class MasterTableViewController : UITableViewController {
var items = ["item 1", "item 2", "item 3"]
@IBAction func doAdd(sender: AnyObject) {
self.items.append("item (self.items.count + 1)")
self.tableView.reloadData()
}
...
Figure 3-39. Navigation bar right button
Build and run (+R) the app to see the MasterDetail iOS app in action. You should see that your app looks like the preview shown previously in Figure 3-36.
UITableView
Previously, you used UITableViewController, which is a regular UIViewController with a prewired tableView object in it. The prewired tableView also prewires the dataSource and delegate to the host UITableViewController, which simplifies some coding for you (actually not that much, in my opinion). The prewired tableView takes up the whole screen, which may not be what you want. More often, I choose to do the following instead of using UITableViewController.
The previous MasterTableViewController is essentially equivalent to the code in Listing 3-10, and you still implement the same methods declared in UITableViewDataSource and UITableViewDelegate.
Listing 3-10. Explicitly Implement Table View Protocols
class MasterTableViewController2 : UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
...
UITableViewCell
In iOS, the line items in the UITableView are called cells. You get some free types of UITableViewCell that you can select from the iOS SDK. Previously in the MasterDetail project, you selected the Basic style, which gives you one textLabel in the Table View cell (see Figure 3-37). Right Details, Left Details, or Subtitle styles all give you a second label called detailedTextLabel. You can also set a left image icon and other TableViewCell attributes, as shown in Figure 3-37.
Still, it is the data source’s responsibility to render the cell with data: You programmatically bind each UITableViewCell with data by implementing the tableView(cellForRowAtIndexPath) method. Listing 3-11 shows a typical example.
Listing 3-11. TableViewCell Properties
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = ...
cell.textLabel!.text = self.items[indexPath.row]
cell.detailTextLabel!.text = "some detail label"
cell.imageView!.image = UIImage(named: "pointer.png")
cell.accessoryType = UITableViewCellAccessoryType.DetailButton
return cell
}
You can also choose the Custom style and draw the cell freely using the storyboard; you will do so in the next section.
UICollectionView
The MasterDetail project you created fits comfortably on an iPhone, but when it is running on the iPad, it feels like the space is not being utilized efficiently. On iOS platforms, UICollectionView comes to the rescue: it allows multiple columns to organize the master list items, rather than a simple one-dimensional list.
It would be a shame if you didn’t try this variant right now because it is a really useful widget that takes very little extra effort. The key is the UICollectionView class.
Note You can also use UICollectionViewController, which contains a UICollectionView that occupies the whole scene by default. Same choice: UITableViewController versus UITableView, which was discussed earlier (see the “UITableView” section).
Create a new Xcode project using UICollectionView to demonstrate the usage.
Figure 3-40. Collection View dataSource, delegate, and IBOutlet connections
Figure 3-41. Size Inspector
Figure 3-42. Drawing the collection view cell
Listing 3-12. SimpleCollectionViewCell Class
class SimpleCollectionViewCell : UICollectionViewCell {
@IBOutlet weak var textLabel: UILabel!
}
Figure 3-43. Creating the detail view controller in MasterDetail storyboard
Listing 3-13. UICollectionViewDataSource and UICollectionViewDelegate Protocols
class MasterViewController : UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
...
// implement UICollectionViewDataSource
var items = ["item 1", "item 2", "item 3", "item 4", "item 5", "item 6", "item 7"]
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as SimpleCollectionViewCell
cell.textLabel.text = self.items[indexPath.row]
cell.backgroundColor = (indexPath.row % 2 == 0) ? UIColor.whiteColor() : UIColor.lightGrayColor()
return cell
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
// implement UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("detail", sender: self)
}
}
Both TableView and CollectionView are versatile. You should look into the data source and delegate protocols to see the rich options offered to developers. Build and run the MasterGridDetail iOS app to see your code live in action, as shown in Figure 3-44.
Figure 3-44. Collection view
Navigation Tabs
WEB ANALOGY
Navigation tabs are easily implemented in a web app by using a UI widget library like jQueryUI or Bootstrap.
Navigation tabs are another popular UX design pattern. Apple’s iOS Human Interface Guidelines suggest using a tab bar to give users access to different perspectives on the same set of data or on different subtasks related to the overall function of your app. Each navigation tab is associated with a view controller. When the user selects a specific tab, the associated view controller presents its own Content View.
Not only does this pattern exist in iOS, but it also widely used in many mobile platforms. You may simply call it tabs in desktop or web apps.
You will create a simple tabbed iOS app (see Figure 3-45) to visualize how this works and how to create it.
Figure 3-45. Preview of the iOS TabbedApp
The key in iOS is the Container View Controller, which is the UITabBarController class. You can use it as is most of the time. If you want to keep some application states in the Container View Controller, simply subclass from it.
Implementing Navigation Tabs
The following instructions walk you through the steps you normally take to implement navigation tabs:
Listing 3-14. SecondViewController Class
class SecondViewController: UIViewController {
...
}
Figure 3-46. First and second scenes in TabbedApp
Figure 3-47. Embed Content Views in Tab Bar Controller (the result on the right)
The app is not complete yet, but you actually can run it now to see the app live. It should look like the preview shown in Figure 3-45.
UITabBarController
As you can see from the previous work (see Figure 3-45), a bottom tab bar comes with UITabBarController. Each tab bar item is associated with a Content View Controller. By selecting a tab bar item, the UITabBarController automatically presents the selected Content View Controller.
Add/Remove a Tab Bar Item
UITabBarController maintains an array of references to its child view controllers. In its Connections Inspector, you can draw a view controller’s outlet in the Triggered Segues section to add the child view controllers to the UITabBarController.viewControllers array property. You can write code to add or remove the child view controller in the runtime, but doing so in a storyboard is easier and more robust for creating static tabs.
Update the Look and Feel of the Tab Bar Items
You should assign a title and an image to the tab bar items. When a UIViewController is added to a tab bar controller, the UIViewController.tabBarItem property represents the tab bar item in the tab bar. You assign appropriate values in the tab bar item’s Attributes Inspector. In the runtime, you use code to update the text and image by setting appropriate tabBarItem properties. Figure 3-48 shows two tab bar items with the same label and without an icon image. Do the following to give your TabbedApp a better look:
Figure 3-48. Adding the Featured system item to Screen One
Note iOS is picky about the right image specs. See the online reference for image specs: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html.
Figure 3-49. Custom tab item with badgeValue
Handle Runtime Behavior
To respond to the runtime behavior programatically, just like most of the UIKit widgets, you implement a delegate protocol: UITabBarDelegate. Continue with the TabbedApp to learn the common tasks for handling UITabBarDelegate runtime events.
Listing 3-15. SimpleTabBarController Class
class SimpleTabBarController : UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view ...
self.delegate = self
}
func tabBarController(tabBarController: UITabBarController!, shouldSelectViewController viewController: UIViewController!) -> Bool {
// you may do something and return true
// Or, return false to not to select viewController
return false;
}
func tabBarController(tabBarController: UITabBarController!, didSelectViewController viewController: UIViewController!) {
// you may do something
}
}
Listing 3-16. Change badgeValue of Other tabBarItem
(self.tabBarController!.viewControllers![1] as UIViewController).tabBarItem.badgeValue = "Zzz"
By the way, the tab bar is located on the bottom screen in iOS instead of at the top in Android or many web sites. In general, platform-specific UX guidelines should never be overlooked. Keep the navigation bar on the bottom in iOS, which is where most iOS users expect it.
Swipe Views
The carousel is a popular UX pattern commonly used in many platforms, including desktop and web apps. On mobile platforms, you use this pattern with swipe gestures to display content page by page. It allows the user to move from item to item efficiently. With animated transitions, it offers a more enjoyable viewing experience. Both iOS and many JavaScript libraries provide framework classes or APIs, as well as a project creation template to promote this navigation UX pattern.
You will be guided to create a simple SwipeViews iOS app (see Figure 3-50) to visualize how this works and how to create it.
Figure 3-50. Preview of the iOS SwipeViews app
As usual for a top-down approach, you want to construct the storyboard scenes to start.
Listing 3-17. Two Classes in ViewController.swift File
import UIKit
class ParentViewController : UIViewController {
@IBOutlet weak var mPageView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class PageContentViewController: UIViewController {
@IBOutlet weak var textLabel : UILabel!
}
Figure 3-51. Main.storyboard in SwipeViews project
Nothing is new yet; you just drew two storyboard scenes with the view controllers to start with. You may build and run the app to make sure it contains no errors. Unlike previous navigation stack or tab patterns to which you can draw segues for view controller transitions, you need to write code to complete the app, which you will do next.
UIPageViewController
The key to implementing the swipe view UX pattern in iOS is the UIPageViewController class, which uses the same data source pattern: you implement a data source that is responsible for providing Content Views populated with data. Continue with the following steps in the SwipeViews Xcode project:
Listing 3-18. Add the data and pageNo Properties
class PageContentViewController: UIViewController {
@IBOutlet weak var textLabel : UILabel!
var data = ""
var pageNo = 0
override func viewDidLoad() {
self.textLabel.text = data
}
}
Listing 3-19. ParentViewController Implements dataSource and delegate Protocol
class ParentViewController : UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
...
// implement data source
let items = ["Page: 1", "Page: 2", "Page: 3"]
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var pageNo = (viewController as PageContentViewController).pageNo
if pageNo > 0 {
var vc = self.storyboard!.instantiateViewControllerWithIdentifier("PageContentViewController") as PageContentViewController
vc.data = self.items[pageNo-1]
vc.pageNo = pageNo - 1
return vc
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var pageNo = (viewController as PageContentViewController).pageNo
if pageNo < self.items.count - 1 {
var vc = self.storyboard!.instantiateViewControllerWithIdentifier("PageContentViewController") as PageContentViewController
vc.data = self.items[pageNo+1]
vc.pageNo = pageNo + 1
return vc
}
return nil
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return self.items.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return (pageViewController.viewControllers[0] as PageContentViewController).pageNo
}
...
Listing 3-20. Setting Up UIPageViewController in viewDidLoad
class ParentViewController : UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
@IBOutlet weak var mPageView: UIView!
var mPageViewController: UIPageViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// a. initialize page view controller, view and gestures
self.mPageViewController = UIPageViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
self.mPageViewController.view.frame = self.mPageView.bounds
self.mPageView.gestureRecognizers = self.mPageViewController.gestureRecognizers
// b. set data source and delegate
self.mPageViewController!.delegate = self
self.mPageViewController!.dataSource = self
// c. set the first page
var vc = self.storyboard!.instantiateViewControllerWithIdentifier("PageContentViewController") as PageContentViewController
vc.data = self.items[0]
vc.pageNo = 0
self.mPageViewController.setViewControllers([vc], direction: .Forward, animated: false, completion: nil)
// d. establish parent-child view and view controller hierachy
self.mPageView.addSubview(self.mPageViewController.view)
self.addChildViewController(self.mPageViewController)
self.mPageViewController.didMoveToParentViewController(self)
}
...
This is all about the UIPageViewController class and the swipe view navigation pattern in iOS. Build and run the iOS SwipeViews app to see your code in action. It should look like the preview shown in Figure 3-50.
Dialogs
WEB ANALOGY
Web application dialogs typically use the built-in window.alert() or window.confirm() method, or they use a modal dialog via an add-on UI library.
Generally, you use the dialogs UX pattern to give users quick feedback or to request a simple confirmation of choices. Dialogs normally sit on top of the current screen while that screen remains partially visible or dimmed. This creates a visual effect that is meant to get more user attention without losing the current context.
Create an Xcode project to demonstrate the uses of dialogs and common programming tasks.
Figure 3-52. Dialogs storyboard
Listing 3-21. Create IBAction Methods for the Alert and Pop-up Buttons
import UIKit
class ViewController: UIViewController, UIAlertViewDelegate {
...
@IBAction func doAlert(sender: AnyObject) {
// TODO
}
@IBAction func doPopup(sender: AnyObject) {
// TODO
}
}
Nothing should be new to you here, but this Dialogs Xcode project should give you a refresher on dialog topics.
UIAlertController
WEB ANALOGY
The window.alert() method halts code execution and offers only one action for the user: to close the alert dialog and continue.
Figure 3-53 shows the standard Android and iOS alert dialogs side by side. On iOS, you use UIAlertController that replaces UIAlertView since iOS 8.
Figure 3-53. Android (left) and iOS (right) alert dialogs
Contiune with the Xcode Dialogs project and add the following code to learn the UIAlertController code:
Listing 3-22. Present UIAlertController
@IBAction func doAlert(sender: AnyObject) {
var alert = UIAlertController(title: "My title", message: "My message", preferredStyle: UIAlertControllerStyle.Alert)
// add action buttons
var actionCancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel,
handler: {action in
// do nothing
})
var actionOk = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default,
handler: {action in
println((alert.textFields![0] as UITextField).text)
})
alert.addAction(actionCancel)
alert.addAction(actionOk)
// add text fields
alert.addTextFieldWithConfigurationHandler({textField in
// config the UITextField
textField.backgroundColor = UIColor.yellowColor()
textField.placeholder = "enter text, i.e., Do Ra Me"
})
// UIViewController API to presend viewController
self.presentViewController(alert, animated: true, completion: nil)
}
Everything inside UIAlertController is created programatically. Figure 3-54 shows the UIAlertController code in action.
Figure 3-54. Android iOS UIAlertController
UIPopoverController
On iOS devices with large screens such as the iPad, you can use PopoverController to present a regular viewController as a pop-over. The pop-over can be anchored at a given position to associate the presented dialog with the presenting context. On devices with compact size classes such as the iPhone, the pop-over controller automatically falls back to the regular full-screen view controller. You can also take advantage of the storyboard editor to draw the Content Views and segue instead of writing code.
The following steps demonstrate how to use PopoverController in the Dialogs project:
Figure 3-55. Dialogs storyboard completion
Listing 3-23. GreenViewController Class
class GreenViewController : UIViewController {
@IBAction func doDone(sender: AnyObject) {
// do something and dismiss
self.dismissViewControllerAnimated(true, completion:nil)
}
}
Figure 3-56. Storyboard segue attributes in the Attributes Inspector
Listing 3-24. Perform the mypopover Manual Segue
// In ViewController class
@IBAction func doPopup(sender: AnyObject) {
self.performSegueWithIdentifier("mypopover", sender: nil)
// var nav = self.storyboard!.instantiateViewControllerWithIdentifier("nav") as UIViewController
// var popover = UIPopoverController(contentViewController: nav)
// popover.delegate = self;
// popover.popoverContentSize = nav.view.bounds.size
// popover.presentPopoverFromRect(self.mPopupButton.frame, inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Up, animated: true)
}
You can build and run the Xcode Dialogs project to see the UIPopoverController code in action. UIPopoverController is rendered as a pop-up dialog on an iPad, while the same code renders a full-screen modal screen in compact-sized classes such as the iPhone, as shown in Figure 3-57.
Figure 3-57. The results of the UIPopoverController code on the iPad vs. the iPhone
Summary
To start developing an iOS app, first create storyboard scenes using the wireframe or mock-up to break your app naturally into a structured MVC project in a top-down fashion. The result is a set of Content View–View Controller pairs.
Next, to implement the screen navigation patterns, you draw storyboard segues to connect storyboard scenes. You also choose the appropriate Container View Controller (such as UINavigationController) from the SDK to facilitate the screen transitions. You will dive into the details of each screen in Chapter 4.