Chapter    3

Structure Your App

To implement your software, you will make design decisions based on how you’d like to structure your app in terms of organizing your code into classes. 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 SDK and tools. MVC is also implicitly embedded in Android SDK (with different vocabularies), and if you are used to the top-down approach for creating your Android apps, where you design the application workflow prior to detailing each individual screen, it is even easier for you to switch your programming thinking process between iOS and Android.

In this first step, you are aiming at class-level mapping from the Android counterparts. 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 that can be mapped from their Android counterparts.

Model-View-Controller

ANDROID ANALOGY

  • Content view: Layout files
  • Content view controller: Fragment class
  • Delegate: Java event listener
  • Container view controller: the Activity class that coordinates the child Fragments in it

MVC immediately breaks the 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.

9781484204375_Fig03-01.jpg

Figure 3-1. The iOS MVC design pattern

Although there seem to be no explicit MVC vocabularies defined in the Android SDK, it implicitly enforces separating content view from the content view controller in terms of Layout files and Fragment classes.

In iOS, you explicitly use the MVC vocabularies: 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 only has one screen, you need to decide how to implement navigations and screen transitions among multiple view controllers. You need an optional MVC participant: Container View Controller. In Android, there seems to be no explicit framework class that does the work for you. However, the parent-child relationship between Activity and Fragment makes the parent Activity a natural candidate for the Container View Controller participant. In practice, I use the parent Activity to manage the child Fragments, including for navigation code.

In iOS, the SDK provides several Container View Controllers for screen navigation; you simply choose the appropriate Container View Controller class and let the iOS framework facilitate the tasks for you.

I’ll start with content view and content view controller, then talk about the Container View Controller next.

Content View

ANDROID ANALOGY

Android Layout file.

A content view provides a visible area so that users can interact with the app. The content view defines how to render itself with contents and can interact with user actions. To create content views in iOS, use Xcode storyboard.

Recall that in the iOS HelloMobile app, you drew the UI widgets in the storyboard scene in a very similar fashion to what you normally do in ADT: drag and drop 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 (see Figure 2-14). Creating adaptive content views for various screen sizes is a common task in both iOS and Android. In Android, you achieve this via alternative layout resources and layout managers. In iOS, you essentially use iOS platform features for the same purposes:

  • Auto Layout: This works best with responsive UX designs that are agnostic/adaptive to screens sizes.
  • Size classes: These provide ultimate flexibility for customizing the screens for different screen sizes.

Auto Layout

ANDROID ANALOGY

RelativeLayout.

You can think of iOS Auto Layout as Android RelativeLayout: you position each UI widget by aligning or spacing it relative to neighbors or the parent view.

The three widgets in the current iOS HelloMobile project are not positioned properly in landscape mode (see Figure 2-14). Use iOS Auto Layout to fix this while learning its uses.

While extremely powerful, some Xcode editors or operations are collapsed in the menus and could be difficult to locate for a beginner. Figure 3-2 depicts some quick tips:

  • If you cannot find any editor or navigator, go to View image ... in the Xcode top menu bar.
  • Auto Layout operations are grouped in Editor image ... in the Xcode top menu bar.
  • There are four small buttons in the bottom toolbar of the storyboard editor. They offer quick Auto Layout operations and give you some visual hints of what they are.
  • Assistant Editor that you will use next.
  • The view selector in the Utility area allows you to switch between several inspectors. You will use them a lot in the next chapter, too.

9781484204375_Fig03-02.jpg

Figure 3-2. Storyboard Auto Layout operations in Xcode

Continue working on the HelloMobile project. Do the following:

  1. Storyboard Preview in Assistant Editor is extremely useful for immediately seeing any changes for the selected storyboard scene:
    1. Select the main.storyboard file and open Assistant Editor (see the right pointer in Figure 3-3).
    2. Click the Assistant menu button to select Preview (see the left pointer in Figure 3-3).

    9781484204375_Fig03-03.jpg

    Figure 3-3. Two steps to reach the Xcode storyboard Preview

  2. Horizontal Center the TextField in Container to create an x-alignment constraint as shown in Figure 3-4:
    1. Select the TextField in the storyboard editor.
    2. In the Xcode top menu bar, select Editor image Align image Horizontal Center in Container.

    9781484204375_Fig03-04.jpg

    Figure 3-4. Using Horizontal Center in Container on the TextField in Auto Layout editor

  3. Use Vertical Center in Container to create a y-alignment constraint (see Figure 3-5):
    1. From the Xcode top menu bar, select Editor image Align image Vertical Center in Container.
    2. In the Attributes Inspector in the Utility area, to position the TextField at the one-sixth of the view height instead of half, you can apply a Multiplier of 3.

    9781484204375_Fig03-05.jpg

    Figure 3-5. Creating constraint y-alignment constraint using a multiplier

    Note  Use Multiplier or Constant to offset the second item position:

    (first item center position) == (second item center) * multiplier + constant

  4. Select Resolve Auto Layout issue image Update Frames as shown in Figure 3-6.

    9781484204375_Fig03-06.jpg

    Figure 3-6. Using Update Frames to reposition the UI widgets based on constraints

  5. Select the Hello World! label and click the Pin button to add multiple constraints for appropriate spacing and widgets height as shown in Figure 3-7:
    1. Pin the Top space to the nearest widget, the TextField, with 48.5 pixels.
    2. Pin both Leading and Trailing spaces to the nearest widget, its parent View, with 60 pixels.
    3. Pin its Height to be 21 pixels.

    9781484204375_Fig03-07.jpg

    Figure 3-7. Spacing Hello World! label relative to its neighbors

    Note  The combined constraints need to make sense without ambiguity. You can set priority to each individual constraint. However, if you start using priority to resolve conflicts, you might want to think about using fewer constraints for your purpose.

  6. Select the Hello... button. You can align it similar to the TextField:
    1. Use Horizontal Center in Container to create an x-alignment constraint.
    2. Use Vertical Center in Container to create a y-alignment constraint with Multiplier to be 0.75.

Using the Auto Layout with responsive UX designs immediately provides 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.

9781484204375_Fig03-08.jpg

Figure 3-8. Auto Layout with responsive UX design

Size Classes

ANDROID ANALOGY

Provide alternative layout resources for different 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 ways. For example, it is fairly common to portray landscape view different from portrait due to different aspect ratios, or 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. The concept is very similar to Android alternative-layout resources for different screen sizes. 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

image

You can provide all the implementation for all size classes all in one storyboard!

Recall the iOS HelloMobile app—it only works in iPhone portrait mode (see Figure 2-14) and it disabled the Size Classes feature. Now you should enable Size classes to demonstrate its use. Do the following:

  1. As shown in Figure 3-9, enable the Use Size Classes (bottom pointer) in the File Inspector (accessed as shown by the top two pointers).

    9781484204375_Fig03-09.jpg

    Figure 3-9. Enabling Use Size Classes

  2. Use Assistant Editor to preview iPhone and iPad screens. Size Classes could be overwhelming in the beginning, but I found the previews very helpful (see Figure 3-10).
    1. The scenes are converted to the most adaptive size class: (wAny hAny). The Auto Layout constraints are also preserved in this size class. You immediately get the iPad scene working as expected.

    9781484204375_Fig03-10.jpg

    Figure 3-10. Size Classes preview

  3. Click the Size Class control to select the size class (see Figure 3-11).
    1. Hover your mouse to see the highlight and title changes. Comparing with Table 3-1, you can select the appropriate row and column that targets specific size classes. The default is Any Width | Any Height, which is applied to all the size classes to start with.

    9781484204375_Fig03-11.jpg

    Figure 3-11. Using the size class selector to select a specific size class

  4. To provide a specific layout for an iPhone landscape scene, select the Compact Width | Compact Height as shown in Figure 3-12.

    9781484204375_Fig03-12.jpg

    Figure 3-12. Compact Width | Compact Height for iPhones in landscape

  5. To demonstrate the powerful Size Class feature, start fresh for the compact-compact size class. From the top menu bar, select Clear Constraints in View Controller from Editor image Resolve Auto Layout Issues. This only clears the Auto Layout constraints in the selected size class; you can see the constraints still there but grayed out.
  6. Drag the widgets to reposition them, just to get a quick idea—you don’t need to be precise (see Figure 3-13).
    1. Since you are providing a custom layout explicitly for iPhones in landscape, you actually can draw the positions precisely and let storyboard do the rest by choosing Reset to Suggested Constraints in View Controller in the top menu bar from Editor image Resolve Auto Layout Issues.
    2. If you tried the preceding step, select Clear Constraints in View Controller again to have a clean start for creating Auto Layout constraints.

    9781484204375_Fig03-13.jpg

    Figure 3-13. Two-sided view for compact height (iPhone landscape mode)

  7. For the TextField, add the following Auto Layout constraints:
    1. Use Horizontal Center in Container to create an x-alignment constraint with Multiplier, 2.
    2. Use Vertical Center in Container to create a y-alignment constraint with Multiplier, 1.5.
    3. From the top menu bar, select Update Frame at Editor image Resolve Auto Layout Issues.
    4. To update the existing constraints, either select the constraint from the storyboard navigator, or select the widget on the scene first to see and click on the guided line in the storyboard scene. Use Attributes Inspector in the Utility area (see Figure 3-14) to update any constraint attribute.

    9781484204375_Fig03-14.jpg

    Figure 3-14. Updating the Auto Layout constraint

  8. For the Label, add the following Auto Layout constraints in the same way as in step 7:
    1. Use Horizontal Center in Container to create an x-alignment constraint with Multiplier, 2.
    2. Use Vertical Center in Container to create a y-alignment constraint with Multiplier, 0.75.
    3. From the top menu bar, select Update Frame at Editor image Resolve Auto Layout Issues.
  9. For the Button, add the following Auto Layout constraints in the same way as in step 7:
    1. Use Horizontal Center in Container to create an x-alignment constraint with Multiplier, 0.67.
    2. Use Vertical Center in Container to create a y-alignment constraint with Multiplier, 1.
    3. From the top menu bar, select Update Frame at Editor image Resolve Auto Layout Issues.

All the device classes in previews look good as expected (Figure 3-15).

9781484204375_Fig03-15.jpg

Figure 3-15. Device classes previewed in the storyboard editor

You can run the app in all emulators to see the work in action. The iPhone 4s emulator is shown in Figure 3-16.

9781484204375_Fig03-16.jpg

Figure 3-16. iPhone4s portrait and landscape size class

Content View Controller

ANDROID ANALOGY

Fragment.

The Content View Controller participant pairs with a content view (see Figure 3-1). In both the iOS and Android programming paradigms, the content view normally is created statically (i.e., by layout.xml or 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. You normally subclass Fragment to create your Content View Controllers in Android. In iOS, you create a class subclassing from UIViewController for the same purpose.

Your primary Content View Controller tasks are:

  • Pair with its own content view
  • Keep object references to the UI widgets in content view
  • Implement methods to respond to widget events.

In iOS, you normally use the storyboard editor to connect the UI widgets or events to your code to facilitate these common programming tasks.

Pair with Content View

ANDROID ANALOGY

Inflate the layout.xml file in Fragment.onCreateView(...).

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 only renders the initial screen but does not do anything when you click on the Hello... button. You need a functional Content View Controller that can fulfill this responsibility. The Single View Application Template pairs a controller class for you already: ViewController.swift. To demonstrate the whole subject, don’t use this class; instead, do the following to create our own class:

  1. Create a new file for a new Swift class:
    1. Right-click on the HelloMobile folder in the Navigator area, then select New File ... image iOS image Source image Swift File (see Figure 2-3).
    2. Save the file as HelloViewController.swift.
    3. Create the HelloViewController class subclassed from UIViewController as shown in Listing 3-1.

    Listing 3-1. HelloViewController Class Skeleton

    import UIKit
    class HelloViewController: UIViewController {
      // TODO
    }
  2. Pair the storyboard scene with the HelloViewController class (see Figure 3-17):
    1. Select Main.storyboard to open the storyboard editor.
    2. Select the view controller in the storyboard scene and open the Identity Inspector in the Utility area.
    3. Enter HelloViewController in the Custom Class field to pair the storyboard scene with the HelloViewController class.

9781484204375_Fig03-17.jpg

Figure 3-17. Identity Inspector to pair with view controller

Specifying the custom class in Identity Inspector is all you need to pair with the content view controller.

Interact with Content View

ANDROID ANALOGY

In the Fragment view controller class,

rootView.findViewById(...) to get the object reference.

Register event listener using the setOnXxxListener(...) methods.

Generally in both Android and iOS, 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 Connections Inspector to create IBOutlet and IBAction to facilitate this common programming task by drawing connections to your code in the Swift class:

  • IBOutlet: the view controller property that is connect to the widgets in the storyboard scene.
  • IBAction: the view controller method that is called when the widget events occur.

The following walks you through the steps to connect the UI widgets and delegates action events to your controller class:

  1. Select Main.storyboard to open the storyboard editor.
    1. Open the storyboard Assistant Editor. The HelloViewController class should automatically open in the assistant editor.
    2. Sometimes the right file may not be opened automatically in the Assistant Editor, so you may need to select the right file manually (see Figure 3-18).

    9781484204375_Fig03-18.jpg

    Figure 3-18. Selecting a file manually in Assistant Editor

  2. Select the TextField in the storyboard scene (the left pointer in Figure 3-19), and open the Connections Inspector (the right pointer in Figure 3-19) as shown in Figure 3-19 (make sure the Utility Area is unfolded).

    9781484204375_Fig03-19.jpg

    Figure 3-19. Opening the Connections Inspector

  3. Create an IBOutlet for the TextField in the storyboard scene (see Figure 3-20).
    1. Drag the circle next to New Referencing Outlet with three fingers (or hold the left trackpad button at the same time) and drop it inside the class. You should see the line from the circle as shown in Figure 3-20.
    2. Enter the connection name (i.e., mTextField). This creates a property in the Swift class.

    9781484204375_Fig03-20.jpg

    Figure 3-20. IBOutlet in Connections Inspector

  4. Repeat steps 2 and 3 to create mLabel and mButton IBOutlets.
  5. Create an IBAction for the button touch down events.
    1. Drag the circle next to Touch Down in Sent Events section and drop it inside the Swift class (see Figure 3-21).

      9781484204375_Fig03-21.jpg

      Figure 3-21. Creating an IBAction in the Connections Inspector

    2. Enter the method name: i.e., onButtonTouchDown. This creates a method stub in the Swift class.
    3. Add the Say-Hello code to complete the IBAction method implementation in HelloViewController.swift as shown in Listing 3-2.

      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 simulators to see the code in action.

You’re almost done with the MVC topics. Just one more small lecture before we get into more fun stuff: UIViewController lifecycle events.

UIViewController Life Cycle

ANDROID ANALOGY

Fragment life cycle.

Similar to the Android Fragment class, lifecycle 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 both iOS and Android. The iOS lifecycle concept may not be a beginner topic, but it is easy for Android developers to pick up because the purpose is the same: they want to perform certain computing tasks at the right time, with which Android developers are already familiar.

In Android, the lifecycle events are described as the states of the view controller itself. In iOS, these lifecycle events are directly related to the content view events, so you actually can visualize the effects better because they are directly related to the view-rendering process.

Implementing these view events is essentially the same as writing your Fragment lifecycle callback methods: you can choose to override these inherited system methods to receive timely callbacks if you wish.

viewDidLoad

ANDROID ANALOGY

Fragment.onCreate() and onCreateView().

iOS system calls the viewDidLoad() method when the view controller loads its content view from the storyboard scene. You commonly put the initialization code here.

viewWillAppear

ANDROID ANALOGY

onStart().

iOS system calls the viewWillAppear() method when the view is about to appear. Generally, you can safely translate the Android onStart() into this method.

viewDidAppear

ANDROID ANALOGY

onResume().

iOS system calls the viewDidAppear() method when the view becomes visible. Generally, you can safely translate the Android onResume() into this method.

viewWillDisappear

ANDROID ANALOGY

onPause().

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()

ANDROID ANALOGY

onStop().

The system calls the viewDidDisappear() method when the content view is not visible.

When implementing these lifecycle events, you almost always will want to call the corresponding super.viewXXX(), just like Android.

Screen Navigation Patterns

You commonly use multiple screens to convey hierarchical information to and interact with users. Considering the relatively small mobile screens, it is even 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 app.

This section will focus on the most common screen navigation patterns supported in both iOS and Android.

Storyboard Segue

Segue, pronounced “seg-way”, is a type of a connection in 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 logics 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 the steps of a storyboard segue:

  1. Create a new Xcode project using Single View Application with a product name of Segues. (See Chapter 2, “iOS Project Anatomy” for step-by-step instructions)
  2. Open Main.storyboard in the storyboard editor. It should look like Figure 3-22 when done.
    1. Add two Button widgets to the existing scene: one for Action Segue and the other for Manual Segue.
    2. Drop two ViewControllers onto the storyboard from Object Library to add two storyboard scenes. Add a UILabel to each scene with titles “From Action Segue” and “From Manual Segue,” respectively.

    9781484204375_Fig03-22.jpg

    Figure 3-22. Segues preparation

  3. Create an Action Segue from the Action Segue Button to the From Action Segue scene.
    1. Select the Action Segue Button and open the Connections Inspector in the Utility area.
    2. Drag the action outlet in the Triggered Segue section to From Action Segue view controller as shown in Figure 3-23.

      9781484204375_Fig03-23.jpg

      Figure 3-23. Creating an Action Segue

    3. Select Show for transition type.
  4. Select the segue (see the pointer in Figure 3-24) and enter the name of the segue Identifier in the segue Attributes Inspector (i.e., actionSegue), as shown in Figure 3-24.

    9781484204375_Fig03-24.jpg

    Figure 3-24. Selecting the segue and setting up the attributes

  5. Create a Manual Segue from the presenting controller to the From Manual Segue view controller as shown in Figure 3-25:
    1. Select the presenting View Controller and open the Connections Inspector in the Utility area.
    2. Drag the circle (outlet) in the Manual Triggered Segue section to the From Manual Segue view controller.
    3. Select Show for transition type.
    4. Select the segue and enter the name of the segue Identifier in the segue Attributes Inspector (i.e., manualSegue).

    9781484204375_Fig03-25.jpg

    Figure 3-25. Creating a Manual Segue

  6. Set up Manual Segue to be performed programmatically when the Manual Segue button is selected:
    1. Create an IBAction to ViewController class with the name onManualSegueTouchDown.
    2. In the onManualSegueTouchDown(...) method, use the code in Listing 3-3 to perform the 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

The storyboard segues perform screen transitions nice and easy 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:

  1. Create a PresentedViewController class with a property to receive data. Listing 3-4 simply prints the received data in the viewDidLoad() method.
    1. Specify the PresentedViewController class in the Identity Inspector to pair with the From Action Segue storyboard scene (see Figure 3-17 for details).
    2. Pair the From Manual Segue storyboard scene with the PresentedViewController class as well.
    3. Add a property, data, to receive the data from the presenting view controller.

    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)")
        }
      }
      ...
    }
  2. The system invokes a prepareForSegue(...) method in the source view controller. You need to implement this method to receive the callback. To pass data from the source view controller, ViewController, override the prepareForSegue method as shown in Listing 3-5.

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

ANDROID ANALOGY

Activity is the parent of child Fragments.

In iOS, screen navigations are primarily implemented by storyboard segues and the Container View Controller classes from SDK that facilitate the screen navigations. You may create subclasses from these system container view controllers, but normally you can just use them as is.

Navigation Stack

ANDROID ANALOGY

FragmentManager back stack.

The Navigation Stack is widely used to manage screen transitions, and particularly for displaying information hierarchy, such as a master drilldown 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 in the navigation stack.

Create an iOS version of the following simple Android app that has an action bar, as shown in Figure 3-26. This simple Android app does the following:

  • It has three content views.
  • It uses FragmentManager API to create fragment transaction for screen transitions.
  • The device back button automatically removes the FragmentTransaction from back stack.
  • You probably used the Fragment Animation API to implement the slide-in and slide-out animations.

9781484204375_Fig03-26.jpg

Figure 3-26. The Android  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.

  1. Create a new Xcode project using iOS Single View Application template (see chapter 2, “iOS Project Anatomy”). Name it NavigationStack.
  2. Select Main.storyboard to open the storyboard in the Editor area. It has one scene already.
  3. Add two more content view (scenes): drag ViewController from Object Library onto the storyboard twice. Figure 3-27 depicts the storyboard containing three scenes.

    9781484204375_Fig03-27.jpg

    Figure 3-27. Three storyboard scenes in the NavigationStack project

  4. Use the counterpart Android Screen One layout to guide you as you update the first storyboard scene:
    1. Add a Label from the Object Library. Change its font size to 30 and text to “Screen One”, and center its alignment with the Attributes Inspector in the Utility area.
    2. Add Auto Layout constraints to center the label as shown in Figure 3-28.

      9781484204375_Fig03-28.jpg

      Figure 3-28. Screen One label with center constraints

    3. Add the Next button with the right and bottom space constraints to anchor the position as shown in Figure 3-29.

      9781484204375_Fig03-29.jpg

      Figure 3-29. Next button with alignment constraints

  5. Repeat step 4 to add a “Screen Two” Label and the Next button to the Screen Two scene.
  6. Repeat step 4 to add a “Screen Three” Label to the Screen Three scene. Figure 3-30 shows the UI widgets added to storyboard.

    9781484204375_Fig03-30.jpg

    Figure 3-30. Three scenes with widgets in NavigationStack project

  7. Create an Action Segue from the Next button in Screen One to the Screen Two scene, and another Action Segue from the Next button in Screen Two to the Screen Three scene.

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, just repeated steps that create storyboard scenes, UI widgets, Auto Layout constraints, and segues that connect them together.

9781484204375_Fig03-31.jpg

Figure 3-31. NavigationStack storyboard

UINavigationController

ANDROID ANALOGY

FragmentTransaction Back Stack

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, the 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).

9781484204375_Fig03-32.jpg

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:

  • Add a Navigation Controller from Object Library.
  • Connect the root view controller segue (in Connections Inspector) to the first view controller.

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:

  1. Select Screen One View Controller from the storyboard.
  2. In the Xcode menu bar, select Editor image Embed In image Navigation Controller as shown in Figure 3-33.

    9781484204375_Fig03-33.jpg

    Figure 3-33. Creating a Navigation Controller

  3. This creates a Navigation Controller and connects the root view controller segue to the Screen One view controller (see Figure 3-34).
    1. The Navigation Controller has a NavigationBar.
    2. The root view controller automatically gets a NavigationItem where you can add a center title and a rightBarButtonItem.

    9781484204375_Fig03-34.jpg

    Figure 3-34. Navigation Controller scene and root view controller connection

  4. Select the NavigationItem in Screen One to add title text (i.e., Navigation Stack), as shown in Figure 3-35.
    1. The title on the NavigationBar also affects the Back button title. iOS automatically updates the button title attribute to reflect where the Back button is going. The button text defaults to Back if the title is not assigned in the view.

    9781484204375_Fig03-35.jpg

    Figure 3-35. Title on the Navigation Item

    Note  The title on the Android Action bar is meant for the App identity. The iOS navigation bar title is more meant for the screen title.

  5. Optional: The title and the rightBarButtonItem need to be installed on NavigationItem as you seen in previous step. If you want to set the title attribute or the rightBarButtonItem to Screen Two or Three, you need to add a NavigationItem to Screen Two or Three view controller first.

Note  The title can be derived from the view controller title when Navigation Item is not being configured.

Build and run the app to see the live app in action (see Figure 3-36).

9781484204375_Fig03-36.jpg

Figure 3-36. The final NavigationStack app

Master List with Details Drilldown

ANDROID ANALOGY

ListView and GridView.

Many apps need to display a list of items that users can tap 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. Both Android and iOS provide guidelines and offer system APIs to promote consistency by making the implementation easy for developers. In fact, both Xcode and ADT supply project templates for creating apps with this UX pattern.

In ADT, you can get the following master-detail app using the ADT Master/Detail Flow template. Initially, it presents the master list with three items. The app shows the detailed screen with the selected item content. You will port this app (see Figure 3-37) to the iOS platform.

9781484204375_Fig03-37.jpg

Figure 3-37. Android master list detailed drilldown

UITableViewController

ANDROID ANALOGY

The implementation concept appears very similar with different vocabularies:

  • ListFragment = UITableViewController
  • List View = Table View
  • List View Item = Table View Cell
  • List Adapter = Data Source

Even the adapter/data source implementations are sort of similar, too.

To port this Android app to iOS, you need two storyboard scenes: one for the master list and the other for the detailed content view. To have a fresh start, create a new iOS project:

  1. Launch Xcode and create a new application using the usual Single View Application template, and name it MasterDetail.
    1. You get a ViewController class that already pairs with a scene in Main.storyboard. Use this pair for the detail view screen.
  2. Create the master list storyboard scene and draw a segue to connect the two scenes. Figure 3-38 depicts the result of the storyboard work.
    1. Drag the UITableViewController from the Object Library, and drop it in the storyboard editor.
    2. Create a Manual Segue from the UITableViewController to the detailed view controller with the Show transition type (see Figure 3-25). Always give the segue an identifier, such as detail (see Figure 3-24).
  3. Select the Table View Cell in the UITableViewController scene and open the Attribute Inspector to configure the Table View Cell (see pointers in Figure 3-38):
    1. Style: Select Basic (or others to see what they are in the editor).
    2. Identifier: Enter mycell. Always give it an identifier. You need it to create reusable cells, just as the Android recycled list item does.
    3. Accessory: Select Detail.
    4. Optionally, you may add an image that shows the icon on the left.

    9781484204375_Fig03-38.jpg

    Figure 3-38. TableViewCell attributes

  4. Embed the UITableViewController in a Navigation Controller (see Figure 3-33 in Navigation Stack). You normally use a Navigation Stack pattern for the screen transitions.
  5. Check the Is the Initial Scene in the Navigation Controller Attributes Inspector (see Figure 3-39) to make the Navigation Controller the starting view controller of this app.

    9781484204375_Fig03-39.jpg

    Figure 3-39. MasterDetail storyboard

  6. Create a MasterTableViewController Swift class in the ViewController.swift file as shown in Listing 3-6.
    1. Subclass MasterTableViewController from UITableViewController.
    2. Pair this MasterTableViewController class with the Table View Controller scene in the Identity Inspector (see Figure 3-17).

Listing 3-6. MasterListTableViewController Class

class MasterTableViewController : UITableViewController {
  // TODO
}

Note  In Swift, the class doesn’t need to be in its own file. I choose to put in it the existing ViewController.swift file for no particular reason. If you are used to the Java way, you may create a new file to host this class.

UITableViewDataSource

ANDROID ANALOGY

android.widget.Adapter.

To populate the items in the Table View, you implement a Data Source: 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:

  1. Implement tableView(tableView,numberOfRowsInSection) to return number of items in the tableView.
  2. Implement tableView(tableView, cellForRowAtIndexPath) to return the tableViewCell instance.

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 very common pattern for saving memory with a large amount of items, and iOS makes it easy by offering this method. Make sure you assign a cell identifier in storyboard.

UITableViewDelegate

ANDROID ANALOGY

ListFragment.onListItemClick(...).

To handle Table View item selected events, override the UITableViewDelegate.tableView(tableView, didSelectRowAtIndexPath) method (see Listing 3-8):

  1. Implement UITableViewDelegate.didSelectRowAtIndexPath(...) to perform segues
  2. Implement a prepareForSegue(...) callback to pass data to the detail view controller (see Listing 3-5 for details).

    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]
      }
  3. To add or delete items, you need to explicitly refresh the table view:
    1. Drag and drop a Bar Button Item to the navigation bar and draw an IBAction to create the doAdd() method (Figure 3-40).

      9781484204375_Fig03-40.jpg

      Figure 3-40. Navigation bar right button

    2. To add an item when the Add button is selected, implement the doAdd() method as shown in Listing 3-9. Make sure you call TableView.reloadData() in the main thread to refresh the table view just like the notifyDataSetChanged() does in Android.

    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()
      }
      ...

Build and run (image+R) the app to see the MasterDetail iOS app live in action (see Figure 3-41).

9781484204375_Fig03-41.jpg

Figure 3-41. MasterDetail app screens in iOS

UITableView

ANDROID ANALOGY

android.widget.ListView.

Just like the Android ListFragment class, which is a Fragment containing a ListView, UITableViewController is a regular UIViewController with a pre-wired tableView in it. You will have the same choice to make: whether or not to use UITableViewController, which simplifies some coding for you (actually not that much, IMO). More often, I choose to do the following instead of using UITableViewController:

  1. In storyboard, create a regular View Controller with TableView:
    1. Add a regular ViewController scene.
    2. Add a TableView to the scene. This gives you more flexibility (i.e., to draw the table view at any location).
    3. Connect the table view to an IBOutlet (see Figure 3-20 for details).
    4. Select the TableView and connect delegate and dataSource outlets to the ViewController in Connection Inspector.
  2. Create a Swift class to pair with the content view:
    1. Subclass from the regular UIViewController class.
    2. Implement UITableViewDataSource and UITableViewDelegate protocols.

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 MasterTableViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {
  @IBOutlet weak var tableView: UITableView!
  ...

UITableViewCell

ANDROID ANALOGY

R.id.simple_list_item_1 from Android SDK, or create custom list item layout.

Similar to Android list-view item usages, you get some free types of UITableViewCell that you can select from iOS SDK. Previously in the MasterDetail project, we selected the Basic style, which gives you one textLabel in the Table View cell (see Figure 3-38). 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-38.

You may programmatically configure TableViewCell using 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 “Custom” style, and draw the cell freely using storyboard; you will do so in the next section.

UICollectionView

ANDROID ANALOGY

GridView.

In Android, GridView is just a variant of the master-detail drilldown pattern using a different UI widget to show the master list. However, on tablets or large-screen devices, GridView is widely used due to greater space efficiencies that use multiple columns to organize the master list items, rather than a simple one-dimensional list. On iOS platforms, UICollectionView comes to the rescue.

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 we discussed earlier (see “UITableView” section).

The MasterDetail project you created fits comfortably on an iPhone, but when it is running in iPad, it feels like the space is not being utilized efficiently. Create a new Xcode project using UICollectionView to demonstrate the usage:

  1. Launch Xcode and create a new application using the usual Single View Application template. Name it MasterGridDetail.
    1. You get a scene in the Main.storyboard and a ViewController class pair.
    2. Rename the class to MasterViewController in both the ViewController.swift file and as the custom class name of the view controller scene (see Figure 3-17).
    3. Drag a Collection View from the Object Library and drop it onto the MasterViewController scene. Let it take up the whole space and pin zero spacing to Superview in all four directions from the Xcode menu bar at Editor image Pin image ... .
    4. Embed the MasterViewController controller in the Navigation Controller (see Figure 3-33)
  2. Select the Collection View in storyboard to create connections in Connections Inspector as shown in Figure 3-42:
    1. Connect dataSource and delegate outlets to the MasterViewController.
    2. Open Assistant Editor and connect New Referencing Outlet to MasterViewController property. Name it mCollectionView.

    9781484204375_Fig03-42.jpg

    Figure 3-42. Collection View dataSource, delegate, and IBOutlet connections

  3. Draw your own custom collection view cell in storyboard:
    1. In Attributes Inspector, assign Collection Reusable View Cell Identifier a value (i.e., mycell). There are some attributes you can change safely in the Attributes Inspector, such as the white background color.
    2. To change the size of the cell, select the parent collection view and change the cell size to 150 × 150 in Size Inspector. Figure 3-43 shows other measurements that you can set.

      9781484204375_Fig03-43.jpg

      Figure 3-43. Size Inspector

    3. Add a Label to the cell, make the font size bigger (i.e., 30), center alignment, and add Auto Layout constraints as shown in Figure 3-44.

      9781484204375_Fig03-44.jpg

      Figure 3-44. Drawing the collection view cell

  4. Create a custom Swift class for the collection view cell. Listing 3-12 shows the SimpleCollectionViewCell class:
    1. Create a swift class, SimpleCollectionViewCell, subclassed from UICollectionViewCell.
    2. Select the collection view cell in storyboard and assign the SimpleCollectionViewCell class in Identity Inspector.
    3. Open the Assistant Editor with the SimpleCollectionViewCell class. In the Connections Inspector, connect the Referencing Outlet to create an IBOutlet, and name it textLabel.

    Listing 3-12. SimpleCollectionViewCell class

    class SimpleCollectionViewCell : UICollectionViewCell {
      @IBOutlet weak var textLabel: UILabel!
    }
  5. Create the detail view controller scene in storyboard as shown Figure 3-45:
    1. Add a regular view controller from the Object Library.
    2. Create a manual segue from the Master View Controller scene to the detailed view controller with a Show transition type (see Figure 3-25). Always enter a storyboard segue Identifier (i.e., detail).

    9781484204375_Fig03-45.jpg

    Figure 3-45. Creating the detail view controller in MasterDetail storyboard

  6. MasterViewController must implement UICollectionViewDataSource and UICollectionViewDelegate protocols (see Listing 3-13):
    1. Implement numberOfSectionsInCollectionView(collectionView) to return the section number; it defaults to 1 if not implemented.
    2. Implement collectionView(collectionView, numberOfItemsInSection) to return number of items in each section.
    3. Implement collectionView(collectionView, cellForItemAtIndexPath) to return the collection view cell instance.
    4. Implement collectionView(collectionView, didSelectItemAtIndexPath) to respond to cell selection.

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 very 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-46.

9781484204375_Fig03-46.jpg

Figure 3-46. Collection view

Navigation Tabs

ANDROID ANALOGY

Actionbar Navigation Tabs.

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.

In Android, you normally use Actionbar. Figure 3-47 shows an example; let’s translate it to iOS.

9781484204375_Fig03-47.jpg

Figure 3-47. Android TabbedApp

The key in iOS is the Container View Controller, 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:

  1. Launch Xcode to create a new app using Single View Application template, and name it TabbedApp.
    1. You get an empty scene in the Main.storyboard and a ViewController class.
    2. Rename the class to FirstViewController in both the ViewController.swift file and the class name in Identity Inspector for the storyboard scene.
    3. Draw the content view in the storyboard scene using the Android app as your wireframe.
  2. You need the second content view and view controller pair.
    1. You can copy, paste, and modify from the FirstViewController class. Listing 3-14 shows the SecondViewController class.

      Listing 3-14. SecondViewController Class

      class SecondViewController: UIViewController {
        ...
      }
    2. You can copy, paste, and modify the storyboard scene in the storyboard editor, too. Don’t forget to update the class name in Identify Inspector. Figure 3-48 shows that the storyboard has the Screen One and Screen Two scenes.

      9781484204375_Fig03-48.jpg

      Figure 3-48. First and second scenes in TabbedApp

  3. Add a Container View Controller, UITabBarController, which will be responsible for managing the two content view controllers.
    1. Embed both storyboard scenes in a TabBarController: multiselect both storyboard scenes (press and hold the image key for multiselect) and select the command from Editor image Embed In image Tab Bar Controller in the Xcode menu bar (see Figure 3-49).

9781484204375_Fig03-49.jpg

Figure 3-49. 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 (see Figure 3-50).

9781484204375_Fig03-50.jpg

Figure 3-50. TabbedApp draft

UITabBarController

As you can see from previous work (see Figure 3-49), 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, too.

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. Previously, Figure 3-50 shows two tab bar items with the same label and without an icon image. Do the following to give our TabbedApp a better look:

  1. System provides a set of common tab bar items (System Item in Attributes Inspector). Use them when possible for consistent platform convention. Select the Featured system item for Screen One (see Figure 3-51).

    9781484204375_Fig03-51.jpg

    Figure 3-51. Adding the Featured System Item to Screen One

  2. For Screen Two, select Custom to supply your own tab bar item Title and Image icon (see Figure 3-52).
    1. Enter the Title (e.g., Two).
    2. Create an image icon in images.xcassets (i.e., tab1), and drop a transparent PNG with size about 25 × 25 (max 48 × 32). The icon color is not required, as only the alpha channel is to be rendered.

      9781484204375_Fig03-52.jpg

      Figure 3-52. Custom tab item with badgeValue

      Note  iOS is very 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.

    3. Enter the Image name (e.g., tab1).
    4. You may enter a badge (e.g., New), which will appear on the upper right corner of the icon. Frequently, it is set programmatically in the runtime using the UITabbatItem.badgeValue property.

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.

  1. Create a custom tab bar controller to handle runtime behaviors as shown in Listing 3-15:
    1. Create a SimpleTabBarController class subclassing from UITabBarController.
    2. Declare SimpleTabBarController to implement UITabBarControllerDelegate protocol.
    3. In storyboard, select the tab bar controller and assign the SimpleTabBarController class in the Identity Inspector.

    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 true
      }

      func tabBarController(tabBarController: UITabBarController,
      didSelectViewController viewController: UIViewController) {
        // you may do something
      }
    }
  2. Each child content view controller can access UIViewController.tabBarController and UIViewController.tabBarItem properties. Listing 3-16 shows how to change the second tab’s badgeValue from the FirstViewController.

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. 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 Android provide framework classes, as well as a project creation template to promote this navigation UX pattern.

In Android, you normally use the ViewPager widget. Figure 3-53 shows the Android SwipeViews app that was created using the ADT Swipe View template:

  • The container Activity has a ViewPager to show the swipeable views from a fragment layout.
  • It has one fragment that contains a label that represents the page content.
  • The pager adapter creates the fragment with content for each page.

9781484204375_Fig03-53.jpg

Figure 3-53. Android SwipeViews app

You will port this to iOS. Use the Android app or layout file (see Figure 3-54) as the wireframe to construct the storyboard scenes to start with.

9781484204375_Fig03-54.jpg

Figure 3-54. Android layout.xml file

  1. Launch Xcode to create a new project using the usual Single View Application template, and name it SwipeViews.
  2. Rename the existing class ViewController to ParentViewController in both the ViewController.swift file and the custom class name in the Identity Inspector of the view controller’s storyboard scene.
  3. Drag and drop a UIView onto the storyboard scene from Object Library:
    1. Open the storyboard Assistant Editor and create an IBOutlet in Connections Inspector by connecting the referencing outlet to the paired view controller, and name it mPageView.
    2. Add Auto Layout constraints to pin the edges to nearest neighbors and set the background to be light gray, as shown in the final storyboard (see Figure 3-55).

      9781484204375_Fig03-55.jpg

      Figure 3-55. Main.storyboard in SwipeViews project

  4. You need another content view–view controller pair for the page content.
    1. Create a new PageContentViewController class (see Listing 3-17).

      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
      }
    2. Draw the second storyboard scene (see the screen on the right in Figure 3-55). Make sure you give the Page Content View controller a storyboard ID (e.g., PageContentViewController). You need the ID to programmatically load a controller from storyboard.

Nothing is new yet; just draw 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

ANDROID ANALOGY

ViewPager.

The key to implementing the swipe view pattern in iOS is the UIPageViewController class, which uses the same data source pattern: implement a data source that is responsible for providing content views populated with data. Continue with the folowing steps in the SwipeViews Xcode project:

  1. Modify the PageContentViewController class so it can receive data and present the three simple screens (see Listing 3-18).

    Listing 3-18. Add data and pageNo Properties

    class PageContentViewController: UIViewController {

      @IBOutlet weak var textLabel : UILabel

      var data = ""
      var pageNo = 0

      override func viewDidLoad() {
        self.textLabel.text = data
      }
    }
  2. UIPageViewController works with dataSource and delegate protocol. The parent view controller, ParentViewController, is a legitimate candidate for implementing them (see Listing 3-19).
    1. UIPageViewControllerDataSource is responsible for supplying the content view controller before and after the current content one.
    2. UIPageViewControllerDataSource also defines the optional page count and page selection indicator.
    3. UIPageViewControllerDelegate defines the optional page controller events callbacks.

    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
      }

    }
  3. In the ParentViewController viewDidLoad() method, the following conventional code (see Listing 3-20) sets up the UIPageViewController:
    1. Initialize page view controller.
    2. Set up the dataSource and delegate.
    3. Set the first page view controller.
    4. Establish the parent-child view controller hierarchy.

    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 (see Figure 3-56).

9781484204375_Fig03-56.jpg

Figure 3-56. iOS SwipeViews app

Dialogs

Generally, you use the Dialogs UX pattern to give mobile users quick feedback or to request 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:

  1. Launch Xcode to create a new project using the Single View Application template, and name it Dialogs.
  2. Draw two Button widgets with titles set to Alert and Popup in the storyboard scene as shown in Figure 3-57.

    9781484204375_Fig03-57.jpg

    Figure 3-57. Dialogs storyboard

  3. Open the storyboard Assistant Editor and connect the Send Event image Touch Down outlet in Connections Inspector to create IBAction methods in the paired ViewController class for both buttons (see doAlert(...) and doPopup(...) in Listing 3-21).

Listing 3-21. Create IBAction methods for the Alert and Popup 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

ANDROID ANALOGY

AlertDialog.

Figure 3-58 shows the standard Android and iOS alert dialogs side by side. On iOS, you use UIAlertController. The basic usages are almost identical except the extra title icon on the Android AlertDialog.

9781484204375_Fig03-58.jpg

Figure 3-58. Android (left) vs. iOS (right) alert dialogs

Contiune with the Xcode Dialogs project and add the following code to learn the UIAlertController code:

  1. To show the iOS dialog as depicted in Figure 3-58, add the following code in the ViewController.doAlert(...) method (see Listing 3-22):
    1. Create a UIAlertController instance.
    2. Add UIAlertAction for dialog buttons.
    3. To prompt user input, add TextField to UIAlertController.
    4. Use the regular UIViewController API to present it as a view controller.

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 the UIAlertController is created programatically. Figure 3-59 shows the UIAlertController code live in action.

9781484204375_Fig03-59.jpg

Figure 3-59. iOS UIAlertController

UIPopoverController

ANDROID ANALOGY

DialogFragment.

On iOS devices with large screens such as the iPad, you can use PopoverController to present a regular viewController as a popover. The popover 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 popover 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 the PopoverController in the Dialogs project:

  1. Add a new storyboard scene for the popover.
    1. Create a GreenViewController class as shown in Listing 3-23 and draw a view controller scene in storyboard to pair with it by specifying the class name in Identity Inspector.

      Listing 3-23. GreenViewController Class

      class GreenViewController : UIViewController {
        @IBAction func doDone(sender: AnyObject) {
          // do something and dismiss
          self.dismissViewControllerAnimated(true, completion:nil)
        }
      }
    2. Embed the view controller in a Navigation Controller (Editor image Embed in from the Xcode menu bar) to take advantage of the navigation bar title, or any Navigation Controller features.
    3. In the Navigation Controller Attributes Inspector, change the Simulated Metrics Size to Freeform, and then change the Simulated Size to 250 × 300 in the Size Inspector.
    4. In the Navigation Controller Identity Inspector, specify the Storyboard ID, i.e., nav. You always need the ID if you want to instantiate a view controller instance directly from a storyboard (see Listing 3-24).
    5. You normally draw meaningful contents in content view. For simplicity, use the Attributes Inspector to change the GreenViewController view’s Background attribute to be Green.
    6. Draw a BarButtonItem on the right side of navigation bar in the GreenViewController, and connect the Send Actions outlet to the IBAction method in GreenViewController as shown in Listing 3-23. Figure 3-60 depicts the completed storyboard.

      9781484204375_Fig03-60.jpg

      Figure 3-60. Dialogs storyboard completion

  2. To present the GreenViewController, draw a Manual Segue from the presenting ViewController to the parent Navigation Controller of GreenViewController. Specify the segue attributes value as shown in Attributes Inspector in Figure 3-61.

    9781484204375_Fig03-61.jpg

    Figure 3-61. Storyboard segue attributes in the Attributes Inspector

    1. Identifier: mypopover.
    2. Segue type: Present As Popover.
    3. Direction: Up (you may play with other values).
    4. Anchor outlet: Drag the outlet to the Popup button in the presenting view controller.
  3. To perform the mypopover Manual Segue, update the ViewController.doPopup(...) IBAction method as shown in Listing 3-24. Note that the commented code programmatically presents a UIPopoverController without using the storyboard segue.

    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)
    }
  4. You may dismiss the popover by tapping anywhere inside the presenting view controller but outside the GreenViewController content view. To dismiss the popover programmatically, implement GreenViewController.doDone(...) to call the dismissViewControllerAnimated(...) as shown in Listing 3-23.

You can build and run the Xcode Dialogs project to see the UIPopoverController code live in action. The UIPopoverController is rendered as a popup 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-62.

9781484204375_Fig03-62.jpg

Figure 3-62. The results of the UIPopoverController code on the iPad vs. the iPhone

Toasts

For giving users quick feedback, which dismisses automatically, you normally use Toast in Android apps. There is no Android-like Toast widget in iOS. I really like it, but it probably would look Android-ish on iOS. You can always create a small view area with fade-in and fade-out effects programmatically with a timer.

Summary

To port Android to an iOS app, first create storyboard scenes using the counterpart Android app as the wireframe 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 that map to the Android counterpart layout-fragment pairs.

Next, to implement the screen navigation patterns, you draw storyboard segues to connect storyboard scenes. You also choose the appropriate container view controller (i.e., UINavigationController) from SDK to facilitate the screen transitions. The remaining content view and view controller mappings between iOS and Android are trivial. You will dive into the details of each screen in Chapter 4.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset