Chapter    4

Implement Piece by Piece

In the previous chapter, you started with iOS storyboard to lay down the groundwork using navigation patterns. It resulted in a set of connected UIViewController classes set in an MVC framework that mapped to Android counterpart fragments.

In this chapter, you are going to implement each view-controller pair, one piece at a time, with a detailed user interface and business logic that should already be present in the counterpart Android Fragment and the layout file. You will also focus on the following common programming-task mappings from iOS to Android:

  • User interface and common UI widgets
  • Persistent storage options
  • Network and remote services with JSON

User Interface

All those storyboard scenes that you implemented using the screen navigation patterns in Chapter 3 were intentionally very simple. Obviously, a useful mobile app provides rich content and offers better functionality to gracefully interact with users. The user interface will certainly play an important role in the overall user experience.

The techniques and vocabularies for creating meaningful user interfaces for iOS are definitely different from those for Android. UI components are normally platform dependent. You just need to know the usages of the UI widgets and where to look up the platform-specific widget specifications.

On the other hand, there are similarities among many UI frameworks. Both iOS and Android view blocks are structured as a view container (view/parent) model, which has been in the industry for a long time. In iOS, UIView is an object that draws something on the screen with which the user can interact. A UIView is also a container that holds other UIView objects to define the hierarchical layout of the UI.

The view container model appears similar to Android but with differences in how to position a UI widget within its parent view or relative to siblings. Android uses layout managers, whereas iOS uses auto layout.

UIView

The UIView object is the basic building block for UI components. It is the base class for all widgets, such as common widgets like UIButton and UILabel. It is also used as the parent container view.

ANDROID ANALOGY

android.view.View or android.view.ViewGroup.

A visible UIView occupies a rectangular area on the screen and is responsible for drawing and event handling. The UIViewController class has a root view defined in the UIViewController.view property that is inherited by all the view controllers. All UI widgets are special types of UIView that have attributes for the intended look, feel, and behaviors. When drawing the view elements in storyboard, they are added to the parent view, and you use the storyboard view Inspectors to visually edit the view properties.

All the iOS UI widgets inherit from UIView. You can set the inherited UIView attributes in the storyboard view Attributes Inspector. For example, Figure 4-1 depicts the View section in the Attributes Inspector for any UI widgets.

9781484204375_Fig04-01.jpg

Figure 4-1. View section of Attributes Inspector

At runtime, you can programmatically update these properties of the UIView object. UIView is the ultimate super class of all the widgets. It offers a fairly rich API for developers to implement a number of UI related responsibilities:

  • rendering content
  • layout and manage subviews
  • event handling
  • animations

The rest of the chapter demonstrates the common attributes or APIs that you most likely will encounter or that are just good to know beforehand. Before diving into common iOS UI widgets from the iOS SDK, I want to talk about an important related topic, Application Resources, which will be used by UI widgets as well as many other common programming tasks.

Application Resources

ANDROID ANALOGY

Android Application Resources

Most GUI apps are composed of more than programming code—they require other resources, such as images and externalized strings. In iOS, you will encounter similar tasks for how to provide different assets for different device configurations. This will demonstrate how to implement two common use cases in Xcode: the Assets Catalog and externalizing strings.

Assets Catalog

Android developers must be familiar with the concept of providing alternative resources for images. This section will show you how in iOS. As usual, create a new Xcode project and do the following:

  1. Launch Xcode, use the Single View Application template, and name the project CommonWidgets. The project comes with one Asset Catalog, Images.xcassets, that already contains the AppIcon set as shown in Figure 4-2 (left pointer). The editor shows you the icons and pixel resolutions for different device types. Toggle the iOS 6.1 and Prior Sizes check box (right pointer in Figure 4-2) to see the differences in the editor.

    9781484204375_Fig04-02.jpg

    Figure 4-2. AppIcon set in Images.xcassets

  2. You can recreate the icons from the Android counterpart, res/drawable-xxhdpi/ic_launcher.png, with the different image resolutions specified in the editor. Drag and drop the appropriate image files on the guided squares. Figure 4-3 depicts the result.

    9781484204375_Fig04-03.jpg

    Figure 4-3. Recreating Android icons in iOS with Images.xcassets

  3. To add an image asset, click the Add (+) button and select New Image Set as shown in Figure 4-4:
    1. Use the Attributes Inspector to select the type of devices you want to provide.
    2. You may choose to supply the image set by selecting the Universal size class or device-specific types. Either way, 1x, 2x, and 3x should cover all the iOS devices now.
    3. Select the image with 1x resolution and drop it on the right spot (as shown by the pointer under sample in Figure 4-4). Repeat the step for 2x and 3x images.
    4. Give the Image Set a name (e.g., sample). The name is the identifier to access the image from your code or from storyboard.

    9781484204375_Fig04-04.jpg

    Figure 4-4. Adding an image set in Images.xcassets

You have created two image assets. The first, AppIcon, is used for the launch icon on the home screen by default. The other one, sample, can be used by your code or any widgets in storyboard. You can use the sample icon in later exercises.

Note  You can use your favorite image editor to create the images, or download them from www.pdachoice.com/bookassets.

Externalize Strings

Generally, you store string texts in external files. Both Android and iOS actually read externalized strings in a similar manner. In Android, the string files are stored in res/values/strings.xml in XML format. In iOS, they are stored in "key" = "value"; format in .strings files.

To translate the externalized strings from your Android project to an iOS project, do the following:

  1. Create a new file anywhere in your Xcode project. For example, to create a new file in "Supporting Files" folder first, and do image+N (shortcut key for New File).
    1. In the Choose a template screen, select iOS image Resource image Strings File.
    2. Save as Localizable.strings. This is the default file name used by the iOS API.
  2. You may copy and comment out the Android counterpart strings.xml file into your iOS Localizable.strings to start with. Listing 4-1 translates a simple Android string.xml to iOS.

    Listing 4-1. Translation from Android strings.xml to iOS Localizable.strings

    /*
    ==== copied from HelloAndroid Android project ===
    <resources>

     <string name="app_name">HelloAndroid</string>
     <string name="action_settings">Settings</string>
     <string name="hello_world">Hello world!</string>
     <string name="hello_buttn">Hello ...</string>
     <string name="name_hint">Enter a Name, i.e, You</string>

    </resources>
    */

    "app_name" = "HelloAndroid";
    "action_settings" = "Settings";
    "hello_world" = "Hello world!";
    "hello_buttn" = "Hello ...";
    "name_hint" = "Enter a Name, i.e, You";
  3. To read the strings from the Localizable.strings file, use the NSLocalizedString(...) method to retrieve the string by key, as shown in Listing 4-2.

Listing 4-2. Read Strings from iOS Localizable.strings File

// hello_world" = "Hello world!";
var str = NSLocalizedString("hello_world", comment: "")
println(str) // Hello world!

With the strings externalized in a text file, you can translate the text to different languages, a common process to implement I18N. Although I am not going to cover Localization/I18N in detail the concept and process actually are the same as in Android.

Common UI Widgets

UI widgets are the interactive software-control components in the application’s UI, such as buttons, text fields, and so forth. You create screens to contain the appropriate UI widgets to interact with users, to collect information from users, and/or to display information to users.

The iOS UIKit framework provides rich system UI widgets that you “draw” in storyboard. You also “connect” them to the Swift class as IBOutlet properties so that your code can directly use the view object, update its attributes, or invoke the widget methods to provide dynamic application behaviors.

The rest of this section introduces the common iOS UI widgets and compares them with their Android counterparts. Continuing with the CommonWidgets created previously, do the following:

  1. The storyboard already has a storyboard scene that pairs with the ViewController class. This scene won’t be tall enough for all the widgets you’re going to add. Just to enable you to see all the widgets to be added to this scene, change the Simulated Metrics size to Freeform and make it long enough so you can see all the widgets in storyboard.
  2. Select the View Controller from storyboard, and in Size Inspector, change Simulated Size to Freeform, and make the size 320x1500 (Figure 4-5) to give the view enough height to start with.

9781484204375_Fig04-05.jpg

Figure 4-5. Changing the Simulated Metrics size in storyboard to Freeform

Later, you will wrap the whole screen in scroll view in the end so that you can scroll the view up and down.

Note  Don’t bother to implement auto layout for each widget in the beginning. The auto layout constraints will get messed up while setting up UIScrollView. Instead, implement the auto layout constraints after you set up scroll view.

UILabel

ANDROID ANALOGY

android.widget.TextView.

You commonly use UILabel to draw one or multiple lines of static text, such as those that identify other parts of your UI.

Using the Android app as the wireframe, add a UILabel to the iOS CommonWidgets app:

  1. Select Main.storyboard, and drag a UILabel from Object Library to the root View as shown in Figure 4-6. Drag the widget to position the UILabel as shown in the Size Inspector.

    9781484204375_Fig04-06.jpg

    Figure 4-6. Adjusting UILabel size and position

  2. Update UILabel attributes in Attributes Inspector as shown in Figure 4-7:
    1. Text: My simple text label
    2. Alignment: center
    3. The others (Shadow, Autoshrink, etc.) are all safe to play with, too.

    9781484204375_Fig04-07.jpg

    Figure 4-7. Updating UILabel attributes

  3. Open Assistant Editor and connect the IBOutlet in Connections Inspector to your code so that you can update UILabel programmatically. Most of the attributes in Attributes Inspector can be modified in the runtime via the IBOutlet mLabel property, as shown in Listing 4-3.

Listing 4-3. UILabel Properties

...
@IBOutlet weak var mLabel: UILabel!
override func viewDidLoad() {
  super.viewDidLoad()
  // Do any additional setup after loading the view ...
  self.initLabel()
}

func initLabel() {
  self.mLabel.text = "My simple text label"
  self.mLabel.textColor = UIColor.darkTextColor()
  self.mLabel.textAlignment = NSTextAlignment.Center
  self.mLabel.shadowColor = UIColor.lightGrayColor()
  self.mLabel.shadowOffset = CGSize(width: 2, height: -2)
}
...

Build and run the CommonWidgets app to see UILabel live in action (Figure 4-8).

9781484204375_Fig04-08.jpg

Figure 4-8. A simple iOS UILabel look and feel

UITextField

ANDROID ANALOGY

Single-line EditText.

In iOS, UITextField accepts a single line of user input and shows a placeholder text when the user input is still empty. To learn by example, do the following to use UITextField in the CommonWidgets project.

  1. Select Main.storyboard, and drag a UITextField from Object Library to the root View as shown in Figure 4-9. Position the UITextField right under the UILabel.

    9781484204375_Fig04-09.jpg

    Figure 4-9. Changing UILabel size and position

  2. Update its attributes in Attributes Inspector as shown in Figure 4-10:
    1. Placeholder: Hint: one-line text input
    2. Fill in the others as shown in the Attributes Inspector.

    9781484204375_Fig04-10.jpg

    Figure 4-10. Defining UITextField in the Attributes Inspector

  3. Open Assistant Editor and connect the following outlets in Connections Inspector to your code as shown in Listing 4-4:
    1. Connect IBOutlet to the mTextField property so that you can update UITextField programmatically.
    2. Connect the delegate outlet to the ViewController class so that the UITextField sends a message to its delegate object.
    3. Implement UITextFieldDelegate protocol in ViewController. Listing 4-4 shows the common way to dismiss the keyboard when the Return key is pressed.

Listing 4-4. UITextFieldDelegate

class ViewController: UIViewController, UITextFieldDelegate {
  ...
  @IBOutlet weak var mTextField: UITextField!

  // called when 'return' key pressed. return false to ignore.
  func textFieldShouldReturn(textField: UITextField!) -> Bool {
    textField.resignFirstResponder()
    return true
  }
  ...

Note  Other methods are defined in UITextFieldDelegate. image-click the symbol in editor to bring up the class definition. I normally check the method signatures without memorizing them.

Build and run the CommonWidgets app to see UITextField live in action.

UITextView

ANDROID ANALOGY

Multiple-line EditText.

In iOS, UITextView accepts and displays multiple lines of text. To learn by example, do the following to use UITextView in the CommonWidgets project.

  1. Select Main.storyboard, and drag a UITextView from Object Library to the root view as shown in Figure 4-11. Position the widget right under the UITextField.

    9781484204375_Fig04-11.jpg

    Figure 4-11. Changing UILabel size and position

  2. Update its attributes in Attributes Inspector:
    1. Text: multiple lines
    2. Take a look at its Attributes Inspector.  Many attributes are similar to UITextField, but not exact (e.g., no Placeholder).
  3. Open Assistant Editor and connect the IBOutlet in Connections Inspector to your code, mTextView, so that you can update UITextView programmatically. Add a method, logText(...), that prints text to UITextView. You will use it later (see Listing 4-5).

Listing 4-5. UILabel Properties

class ViewController: UIViewController ... {
  ...
  @IBOutlet weak var mTextView: UITextView!
  func logText(text : String) {
    self.mTextView.text = self.mTextView.text + " " + text

    // to make sure the last line is visible
    var count = self.mTextView.text.utf16Count // string length
    self.mTextView.scrollRangeToVisible(NSMakeRange(count, 0))
  }
  ...

UITextView can have multiple lines separated by line breaks. You won’t be able to dismiss the keyboard the same way you normally do for UITextField. Normally, you use another control—for instance, if you have a save button, you may use it to trigger the View.resignFirstResponder() that dismisses the keyboard.

UIButton

ANDROID ANALOGY

android.widget.Button.

In iOS, UIButton, the common Button control widget, intercepts touch events and sends an action message to the delegate. To learn by example, do the following in the CommonWidgets project.

  1. Select Main.storyboard, drag a UIButton from Object Library to the root View, and position the UIButton right under the text view as shown in Figure 4-12.

    9781484204375_Fig04-12.jpg

    Figure 4-12. UIButton position and attributes

  2. Update its attributes in Attributes Inspector (see Figure 4-12):
    1. Just like Android Button, most of the Button attributes are associated with the button states. Select the State Config first: Default
    2. Title:Action Button
    3. Image: sample
    4. Leave the other attributes as shown in Figure 4-12.
  3. Open Assistant Editor and connect the IBOutlet and IBAction in Connections Inspector to your code as shown in Listing 4-6.

Listing 4-6. IBOutlet and Implement IBAction

class ViewController: UIViewController, UITextFieldDelegate {
  ...
  @IBOutlet weak var mButton: UIButton!
  @IBAction func doButtonTouchDown(sender: AnyObject) {
    println(self.mButton.titleForState(UIControlState.Normal))
    self.mButton.setTitle("Click me!", forState: UIControlState.Normal)
    self.logText("Button clicked")
  }
  ...

Build and run the CommonWidgets app to make sure everything is good. When the button is pressed, it simply logs "Button clicked" text in the UITextView (See Figure 4-13).

9781484204375_Fig04-13.jpg

Figure 4-13. Button clicked in UITextView

UISegmentedControl

ANDROID ANALOGY

android.widget.RadioGroup.

In iOS, UISegmentedControl offers closely related but mutually exclusive choices. In Android, RadioGroup offers the same option, but in my opinion, the Android L&F is more of a desktop app or web page style. To show and learn by example, do the following in the CommonWidgets app.

  1. Select Main.storyboard, and drag a UISegmentedControl from Object Library to the root View under the button as shown in Figure 4-14.

    9781484204375_Fig04-14.jpg

    Figure 4-14. UISegmentedControl size and position

  2. Update its attribute in Attributes Inspector (see Figure 4-15):
    1. Style: Bar
    2. Segments: 3
    3. Title: First, Second, and Third for each segment respectfully.
    4. Optionally, you may assign an Image instead of Title for each segment.
    5. You may check the Selected segment (e.g., Segment 0).

    9781484204375_Fig04-15.jpg

    Figure 4-15. UISegmentedControl attributes

  3. Open Assistant Editor and connect IBOutlet and IBAction in Connections Inspector to your code. Frequently, you implement IBAction for the Value Changed events to capture the selections (see Listing 4-7).

Listing 4-7. UISegmentControl IBOutlet and Implement IBAction

class ViewController: ...{
  ...
  @IBOutlet weak var mSegmentedControl: UISegmentedControl!
  @IBAction func doScValueChanged(sender: AnyObject) {
    var idx = self.mSegmentedControl.selectedSegmentIndex
    self.logText("segment (idx)")
  }
  ...

Build and run the CommonWidgets app to see how UISegmentedControl in action. Each segment has a zero-based index (see Figure 4-16).

9781484204375_Fig04-16.jpg

Figure 4-16. UISegmentedControl zero-based index

UISlider

ANDROID ANALOGY

android.widget.SeekBar.

iOS UISlider allows users to make adjustments to a value given a range of allowed values. Users drag the slider left or right to set the value. The interactive nature of the slider makes it a great choice for settings that reflect intensity levels, such as volume, brightness, or color saturation.

To translate the Android SeekBar to the iOS UISlider, do the following:

  1. Select Main.storyboard, drag a UISlider from Object Library, and place it below the UISegmentedControl as shown in Figure 4-17.

    9781484204375_Fig04-17.jpg

    Figure 4-17. Updating the UISlider attributes

  2. Update its attribute in Attributes Inspector (see Figure 4-17):
    1. Values: min 0 and max 100
    2. Min and Max Image.
    3. Min and Max Track Tint.
    4. You may disable Continuous Updates.
  3. Open Assistant Editor and connect IBOutlet and IBAction to your code in Connections Inspector. You will often implement IBAction for the Value Changed event to capture the selections (see Listing 4-8).

Listing 4-8. UISlider IBOutlet and implement IBAction

class ViewController: ...{
  ...
  @IBOutlet weak var mSlider: UISlider!
  @IBAction func doSliderValueChanged(sender: AnyObject) {
    var value = self.mSlider.value
    self.logText("slider: (value)")
  }
  ...

Build and run the CommonWidgets app to see how UISlider in action. As you drag the thumb (the circle on the slider), its value continues to be printed in the UITextView (see Figure 4-18).

9781484204375_Fig04-18.jpg

Figure 4-18. UISlider value updates

UIActivityIndicatorView

ANDROID ANALOGY

android.widget.ProgressBar default style.

In iOS, UIActivityIndicatorView displays a “busy” activity indicator for a task or something else in progress. This is the so-called indeterminate ProgressBar with a spinning wheel in Android. To port the Android indeterminate ProgressBar to iOS, do the following in the CommonWidgets iOS app:

  1. Select Main.storyboard, drag a UIActivityIndicatorView from Object Library, and position it left-aligned and below the UISlider (see Figure 4-19).

    9781484204375_Fig04-19.jpg

    Figure 4-19. UIActivityIndicatorView attributes

  2. Update its attribute in Attributes Inspector as shown in Figure 4-19:
    1. Style: Gray
    2. Color: Default
    3. Behavior: both Animating and Hides When Stopped are commonly enabled.
  3. Open Assistant Editor and connect IBOutlet in Connections Inspector to your code so that you can enable or disable the activity indicator as shown in Listing 4-9.

Listing 4-9. UIActivityIndicatorView IBOutlet

class ViewController: ...{
  ...
  @IBOutlet weak var mActivityIndicator: UIActivityIndicatorView!
  func toggleActivityIndicator() {
    var isAnimating = mActivityIndicator.isAnimating()
    isAnimating ? mActivityIndicator.stopAnimating() : mActivityIndicator.startAnimating()
  }
  ...

Build and run the CommonWidgets app to see the iOS animated activity indicator. You will call the toggleActivityIndicator() method later.

UIProgressView

ANDROID ANALOGY

android.widget.ProgressBar horizontal style.

To show a task with known duration in progress, use UIProgressView to show how far the task has progressed. With this, users can better anticipate how much longer until it completes. To translate the Android horizontal ProgressBar to the iOS UIProgressView, do the following:

  1. Select Main.storyboard, drag a UIProgressView from Object Library, and position it below and left-aligned to the activity indicator as shown in Figure 4-20.

    9781484204375_Fig04-20.jpg

    Figure 4-20. UIActivityIndicatorView attributes

  2. Update its attributes in Attributes Inspector (see Figure 4-20):
    1. Style: Default (or Bar)
    2. Progress: 0.5 (between 0.0 and 1.0)
    3. Progress Tint: Purple.
    4. Track Tint: Yellow.
  3. Open Assistant Editor and connect IBOutlet in Connections Inspector to your code so that you can update UIProgressView programmatically. Modify the UISlider delegate method, doSliderValueChanged(...) as shown in Listing 4-10, to see the progress change visually (see Figure 4-21).

Listing 4-10. UIActivityIndicatorView IBOutlet

class ViewController: ...{
  ...
  @IBAction func doSliderValueChanged(sender: AnyObject) {
    ...
    self.updateProgress(value/100)
  }
  ...
  @IBOutlet weak var mProgressView: UIProgressView!
  func updateProgress(value: Float) {
    self.mProgressView.progress = value
  }
  ...

9781484204375_Fig04-21.jpg

Figure 4-21. UIProgressView update in action

Build and run the CommonWidgets app to visualize iOS UIProgressView in action (see Figure 4-21).

UISwitch

ANDROID ANALOGY

android.widget.Switch, Checkbox, or ToggleButton.

The switch-like widgets are user friendly for presenting mutually exclusive choices. In Android, you may use CheckBox, ToggleButton, or Switch. All are capable for the intended purpose with different L&F.

In iOS, you use UISwitch to allow a user to change values by toggling or dragging the thumb between two states.

To learn UISwitch by example, do the following.

  1. Select Main.storyboard and drag a UISwitch from Object Library. Position it to the right of UIActivityIndicatorView (see Figure 4-22).

    9781484204375_Fig04-22.jpg

    Figure 4-22. UIActivityIndicatorView

  2. Update its attribute in Attributes Inspector (see Figure 4-22):
    1. State: On
    2. You may change any other attributes safely.
  3. Open Assistant Editor and connect IBOutlet and IBAction in Connections Inspector to your code. You will often implement IBAction for the Value Changed event to capture the selections (see Listing 4-11).

Listing 4-11. UISwitch IBOutlet

class ViewController: ...{
  ...
  @IBOutlet var mSwitch: UISwitch!
  @IBAction func doSwitchValueChanged(sender: AnyObject) {
    var isOn = self.mSwitch.on
    self.toggleActivityIndicator()
  }
  ...

Build and run the app and toggle the UISwitch to see the activity indicator’s animation changes (see Figure 4-23).

9781484204375_Fig04-23.jpg

Figure 4-23. iOS UISwitch look and feel

UIImageView

ANDROID ANALOGY

android.widget.ImageView.

In iOS, UIImageView displays one image or a series of images for simple graphic animations. For a simple usage like the CommonWidgets app, all you need to do is specify the image source and the attributes in storyboard for how you want to render the image.

To learn iOS UIImageView by example, do the following:

  1. UIImageView can now render vector-based images! This is a new feature in Xcode 6. I only know that the first page of a PDF is rendered nicely. You can definitely use a bitmap image for this exercise, or create a new image set for a PDF file, as shown by the pointers in Figure 4-24:
    1. Select Images.xcassets to add a New Image Set. Name it pdf.
    2. Set the Type to Vectors in the Attributes Inspector.
    3. Drag a PDF file to the universal slot as shown in Figure 4-24. There is no need to provide 2x or 3x images.

    9781484204375_Fig04-24.jpg

    Figure 4-24. create image set

  2. Select Main.storyboard, drag a UIImageView from Object Library to the view, and position it under the UIProgressView as shown in Figure 4-25.
  3. Update its attributes in Attributes Inspector:
    1. Image: pdf
    2. Mode: Aspect Fit
    3. Select others as shown in Figure 4-25.

    9781484204375_Fig04-25.jpg

    Figure 4-25. UIImageView attributes

  4. Open Assistant Editor and connect IBOutlet in Connections Inspector to your code. Listing 4-12 demonstrate a simple setImage(...) method that assigns an UIImage object to UIImageView.

Listing 4-12. UISwitch IBOutlet

class ViewController: ...{
  ...
  @IBOutlet weak var mImageView: UIImageView!
  func setImage(name: String) {
    self.mImageView.image = UIImage(named: name)
  }
  ...

As you can see in Figure 4-25, there are very few attributes you would need to master. However, when it comes down to creating a UIImage and optimizing size and performance, you want to look into the UIImage class to see how you would construct the UIImage instances from various sources. There are actually iOS frameworks that primarily deal with images, like Quartz 2D or OpenGL. If you know Android OpenGL ES, you definitely want to take advantage of your existing knowledge and explore the counterpart iOS OpenGL framework. If you come from a graphics editing background, Quartz 2D offers a very rich graphics API that will support you for iOS graphics editing tasks.

You may build and run the CommonWidgets iOS app to see UIImageView in action as shown in Figure 4-26.

9781484204375_Fig04-26.jpg

Figure 4-26. UIImageView in iPhone 5

Menu

Menu is frequently used to provide quick access to frequently used actions. It is particularly common in desktop and Android platforms. Although there is no such similarly named feature in the iOS SDK, UIBarButtonItem in UIToolbar or UINavigationBar serves a similar purpose as the Android menu system: quick access.

You might also encounter the Android context and popup menus. Again, there is no such menu system in iOS, but I will show you my iOS choices for porting purposes.

UIBarButtonItem

ANDROID ANALOGY

Options Menu or Action Items in ActionBar.

iOS and Android provides widgets for quick access. In iOS, you commonly use UIBarButtonItem in the navigation bar for a limited number of action buttons that can fit into the fixed space. On the iPhone, you may create a bottom bar, UIToolbar, if all of the buttons in UIBarButtonItem don’t fit on the navigation bar.

To learn and show the UIBarButtonItem in the navigation bar and toolbar by example, do the following in the CommonWidgets project:

  1. Drag a UINavigationBar from Object Library to the view and position it on top of the view. Often, it is simpler to add a NavigationController by selecting the View Controller in storyboard, and from the Xcode menu bar select Editor image Embed In image Navigation Controller. Figure 4-27 depicts the operation results in a new Navigation Controller scene and a Navigation Item in the existing View Controller scene:
    1. Multi-select all the widgets in the scene and reposition them to make room for the top bar.
    2. Update Navigation Item attributes in Attributes Inspector (e.g., enter Title: CommonWidgets).

    9781484204375_Fig04-27.jpg

    Figure 4-27. Navigation Controller and Navigation Bar

  2. Double-click the Navigation Bar to select it, and drag-and-drop a UIBarButtonItem from the Object Library on the right side of the Navigation Bar to add a rightBarButtonItem (see Figure 4-28). Choose a Identifier from selections for those common actions. Or enter a title, such as Action, as shown in Figure 4-28.

    9781484204375_Fig04-28.jpg

    Figure 4-28. UIBarButtonItem attributes

  3. Open Assistant Editor and connect the IBAction in the UIBarButtonItem Connections Inspector to your code (see Listing 4-13).

Listing 4-13. UIBarButtonItem IBOutlet and IBAction

class ViewController: ...{
  ...
  @IBAction func doBarButtonAction(sender: AnyObject) {
    println(">>doBarButtonAction")
  }
  ...

Action Sheet

ANDROID ANALOGY

Context Menu or android.widget.PopupMenu.

In Android, the Context Menu is a floating menu that appears when the user right-clicks an element. The operations and look and feel establish a strong relationship to the context that originates the operations. On the iPad, you may safely choose UIPopoverController (see Chapter 3, UIPopoverController) to present the list of selections, which on the iPhone is automatically presented as full screen.

If you don’t want to use full screen, perhaps for a smaller selection, you can also choose UIActionSheet, which is presented as popover for iPad, in an sheet that emerges from the bottom of the screen.

The key SDK class is UIAlertController, which was introduced in Chapter 3 for alert dialogs (see Listing 3-22). To learn the iOS action sheet by example, modify the previous doBarButtonAction(...) IBAction method as shown in Listing 4-14:

  1. Create an instance of UIAlertController with the style UIAlertControllerStyle.ActionSheet.
  2. You may use Title or Message to establish visual connection to the originating context.
  3. It is common to have a destructive UIAlertAction in red for delete or remove, which is specified with the UIAlertActionStyle.Destructive style.

Listing 4-14. UIAlertController with ActionSheet style

class ViewController: ...{
  ...
  @IBAction func doBarButtonAction(sender: AnyObject) {
    println(">>doBarButtonDone: ")

    var actionSheet = UIAlertController(title: "Action (from bar button item)", message: "Choose an Action", preferredStyle: UIAlertControllerStyle.ActionSheet)

    // add action buttons
    var actionCancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel,
      handler: {action in
      // do nothing
      })

    var actionNormal1 = UIAlertAction(title: "Action 1", style: UIAlertActionStyle.Default,
      handler: {action in
        println(">>actionNormal1")
      })

    var actionNormal2 = UIAlertAction(title: "Action 2", style: UIAlertActionStyle.Default,
      handler: {action in
        println(">>actionNormal2")
      })

    var actionDestruct = UIAlertAction(title: "Destruct", style: UIAlertActionStyle.Destructive,
      handler: {action in
        println(">>actionDestruct")
      })

    actionSheet.addAction(actionCancel) // always the last one
    actionSheet.addAction(actionNormal1)
    actionSheet.addAction(actionNormal2)
    actionSheet.addAction(actionDestruct)

    // UIViewController API to presend viewController
    self.presentViewController(actionSheet, animated: true, completion: nil)
  }
  ...

Build and run the CommonWidgets app to visualize the iOS action sheet (see Figure 4-29).

9781484204375_Fig04-29.jpg

Figure 4-29. UIAlertController with ActionSheet style

The android.widget.PopupMenu is anchored to a view. You can do the same thing in iOS by using iOS UIPopoverController to present a UITableViewController. This is either a perfect translation in iPad or a full-screen table view in iPhone. Or, you may use the iOS ActionSheet style to avoid the full-screen UITableView for the iPhone.

UIPickerView

ANDROID ANALOGY

android.widget.Spinner.

In iOS, UIPickerView displays a set of values from which the user selects. It provides a quick way to select one value from a spinning-wheel–like list that shows all or part of the selections.

In traditional desktop apps or web pages, you normally see a drop-down list, or the android.widget.Spinner in Android, for this purpose, except with one trivial difference: they only show the selected value with the other choices folded.

The iOS UIPickerView uses the same pattern as UITableView DataSource to supply the items. To learn by example, add a UIPickerView widget to our CommonWidgets app and do the following:

  1. Select Main.storyboard and drag a UIPickerView from Object Library. Position it below the UIImageView (see Figure 4-30).

    9781484204375_Fig04-30.jpg

    Figure 4-30. Placing the UIPickerView

  2. Open Assistant Editor and establish UIPickerView outlet connections to your code in Connections Inspector (see Figure 4-31):
    1. Connect IBOutlet to your code.
    2. Connect delegate and dataSource outlets to ViewController class (just like UITableView or any widgets using Data Source).

    9781484204375_Fig04-31.jpg

    Figure 4-31. Connecting the UIPickerView outlets

  3. To implement the UIPickerView delegate and data source, declare the ViewController class to implement UIPickerViewDelegate and UIPickerViewDataSource protocols as shown in Listing 4-15.

Listing 4-15. UIPickerView IBOutlet

class ViewController: ..., UIPickerViewDelegate, UIPickerViewDataSource {
  ...
  @IBOutlet weak var mPickerView: UIPickerView!
  // returns the number of 'columns' to display.
  func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
    return 2
  }

  // returns the # of rows in each component..
  func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return 10
  }

  func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
    return "((component), (row))"
  }

  func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    println("(self.mPickerView.selectedRowInComponent(0))") // before selection
    println("(self.mPickerView.selectedRowInComponent(1))")
    println("((component), (row))") // current selection
  }
  ...

Build and run the app to see iOS UIPickerView in action. The iPhone emulator is too small for all the widgets you have so far. You need an Android-like ScrollView (which you will implement soon). You can run it in the iPad emulator for now (see Figure 4-32).

9781484204375_Fig04-32.jpg

Figure 4-32. UIPickerView in the iPad emulator

Note that if the app looks like a big iPhone while running in the iPad emulator and all the widgets simply scale up, your project deployment info must have been set to iPhone only. Change the deployment device to Universal under Deployment Info, as shown by the pointer in Figure 4-33.

9781484204375_Fig04-33.jpg

Figure 4-33. Changing Devices to Universal in Deployment Info

Play Video

ANDROID ANALOGY

android.widget.VideoView.

Similar to Android, the iOS SDK gives you an easy-to-use API to play video resources from a URL. To play full-screen video, you can use the MPMoviePlayerViewController class, which already has the appropriate content view and media player controls built in. You only need to present the whole view controller. The following method (see Listing 4-16) demonstrates the simplest usage:

  1. Implement the useMoviePlayerViewController() method, which plays a video in MPMoviePlayerViewController view controller (see Listing 4-16):
    1. Create an instance with a URL. Just like Android, it can link to a remote video source. iOS supports HTTP live-streaming protocol (HLS). You can also create a file URL for bundled content, as shown in the commented code in Listing 4-16. Just as you would in Android, make sure the video format is supported. MPEG4 QuickTime is fairly agnostic, and HLS is good for progressive loading.
    2. MPMoviePlayerViewController contains a MPMoviePlayerController property, which is the core class to play video. Almost all the customization is done via this property. You will use this class in a moment.
  2. Earlier you implemented two actions in an ActionSheet (see Figure 4-29). Use the Action 1 button to trigger the useMoviePlayerViewController() method (see Listing 4-16).

    Listing 4-16. useMoviePlayerViewController

    import MediaPlayer
    ...
    class ViewController: ...{
      ...
      @IBAction func doBarButtonAction(sender: AnyObject) {
        ...
        var actionNormal1 = UIAlertAction(title: "Action 1", style: UIAlertActionStyle.Default,
          handler: {action in
            println(">>actionNormal1")
            self.useMpMoviePlayerViewController()
          })
        ...
      }
      ...
      func useMpMoviePlayerViewController() {
    //    var filepath = NSBundle.mainBundle().pathForResource("sample.mp4", ofType: nil)
    //    var fileUrl = NSURL(fileURLWithPath: filepath)
    //    var pvc = MPMoviePlayerViewController(contentURL: fileUrl)

        var contentUrl = NSURL(string: "http://devimages.apple.com/iphone/samples/bipbop/gear3/prog_index.m3u8")
        var pvc = MPMoviePlayerViewController(contentURL: contentUrl)

        pvc.moviePlayer.shouldAutoplay = false;
        pvc.moviePlayer.repeatMode = MPMovieRepeatMode.One

        self.presentViewController(pvc, animated: true, completion: nil)
      }
      ...
  3. To play video in non-fullscreen mode, use the MPMoviePlayerController directly to play video in a View widget:
    1. Select Main.storyboard, drag a UIView from Object Library, and position it below the UIPickerView as shown in Figure 4-34. This is the viewing area that shows the video.

      9781484204375_Fig04-34.jpg

      Figure 4-34. View element for the video

    2. Open the Assistant Editor to Connect IBOutlet in Connections Inspector to your code, mVideoView property.
    3. Create a stored property for the MPMoviePlayerController instance to allow users to seek through, play, or stop the playback.
    4. In viewDidLoad(...), invoke the useMoviePlayerController() method to prepare the video to play.
    5. Use the Action 2 button to start the video. (see Listing 4-17).

Listing 4-17. Using the MPMoviePlayercontroller

class ViewController: ...{
  ...
  override func viewDidLoad() {
    ...
    self.useMoviePlayerController()
  }
  ...
  @IBOutlet weak var mVideoView: UIView!
  var mMoviePlayer : MPMoviePlayerController!
  func useMoviePlayerController() {
    var url = NSURL(string: "http://devimages.apple.com/iphone/samples/bipbop/gear3/prog_index.m3u8")
    self.mMoviePlayer = MPMoviePlayerController(contentURL: url)

    self.mMoviePlayer.shouldAutoplay = false
    self.mMoviePlayer.controlStyle = MPMovieControlStyle.Embedded
    self.mMoviePlayer.setFullscreen(false, animated: true)

    self.mMoviePlayer.view.frame = self.mVideoView.bounds
    self.mVideoView.addSubview(self.mMoviePlayer.view)

    self.mMoviePlayer.currentPlaybackTime = 2.0
    self.mMoviePlayer.prepareToPlay()
  }
  ...
  @IBAction func doBarButtonAction(sender: AnyObject) {
    ...
    var actionNormal2 = UIAlertAction(title: "Action 2", style: UIAlertActionStyle.Default,
      handler: {action in
        self.mMoviePlayer.play()
      })
    ...
  }

Build and run the app. Select Action 1 for full screen and Action 2 to play video embedded in a subview. You don’t have the Android-like ScrollView yet, but you can run it in the iPad emulator for now (see Figure 4-35).

9781484204375_Fig04-35.jpg

Figure 4-35. Playing video full screen vs. embedded in the iPad emulator

WebView

ANDROID ANALOGY

android.widget.WebView.

You can display rich HTML content in your mobile apps on almost all the popular mobile platforms, including iOS, Android, BlackBerry, and Windows phones. This enables you to deliver web content as part of your mobile apps. One common scenario is when you want to provide information in your app that needs to be updated frequently, and you want to host the content online as a web page. To take it one step further, the web content does not have to be remote; you can bundle the web page content with the native app. This enables web developers to leverage their web development skills and create so-called hybrid apps.

With new features from HTML5 and CSS3, many web developers are creating meaningful and interactive web apps that are shortening the gap between native apps and mobile web apps. In iOS, the key SDK class is UIWebView, and it does support many HTML5 and CSS3 features (e.g., Offline Cache and WebSocket, etc.).

As an example, the following steps demonstrate common tasks using UIWebView:

  1. Select Main.storyboard, drag a UIWebView from the Object Library, and position it below the video View (see Figure 4-36). Set its attributes in Attributes Inspector; it is commonly set Scales Page To Fit.

    9781484204375_Fig04-36.jpg

    Figure 4-36. iOS UIWebView attributes in the Connections Inspector

  2. As usual, open Assistant Editor and connect the following outlets in Connections Inspector to your code (see Figure 4-36):
    1. Connect IBOutlet so you can use the widget in your code.
    2. Connect the delegate outlet so your code can intercept UIWebView life-cycle events.
  3. Listing 4-18 demonstrates the programming code commonly used for UIWebView.
    1. Use loadRequest(...) to load the URL. You can also create a file URL to load a local HTML file.
    2. Use loadHTMLString(...) to render simple string text.
    3. Although not demonstrated here, you can also use loadData(...) to render NSData that you normally get from remote contents using NSURLConnection, which I will demonstrate later in the "NSURLConnection" section.

    Listing 4-18. UIWebView Code for Loading a URL or String Text

    class ViewController: ... {
      ...
      override func viewDidLoad() {
        ...
    //    self.showWebPage(url: "http://pdachoice.com/me/webview")
        self.showWebPage(htmlString: "<H1>Hello UIWebView</H1>")
      }
      ...
      @IBOutlet weak var mWebView: UIWebView!
      func showWebPage(#url: String) {
        var req = NSURLRequest(URL: NSURL(string: url)!)
        self.mWebView.loadRequest(req)
      }

      func showWebPage(#htmlString: String) {
        self.mWebView.loadHTMLString(htmlString, baseURL: nil)
      }
      ...
  4. To intercept UIWebView life-cycle events, implement the UIWebViewDelegate protocol as shown in Listing 4-19.

Listing 4-19. UIWebViewDelegate protocol

class ViewController: ... , UIWebViewDelegate{
  ...
  func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    // do something, like re-direct or intercept etc.
    return true; // false to stop http request
  }
  func webViewDidStartLoad(webView: UIWebView) {
    // do something, e.g., start UIActivityViewIndicator
    self.mActivityIndicator.startAnimating()
  }
  func webViewDidFinishLoad(webView: UIWebView) {
    // do something, e.g., stop  UIActivityViewIndicator
    self.mActivityIndicator.stopAnimating()
  }
  func webView(webView: UIWebView, didFailLoadWithError error: NSError) {
    // do something, i.e., show error alert
    self.mActivityIndicator.stopAnimating()
    var alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
    alert.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Cancel, handler: nil))
    self.presentViewController(alert, animated: true, completion: nil)
  }
  ...

Build and run the app to see iOS UIWebView in action. Now, even the iPad Air screen is too small (see Figure 4-37). You need an Android-like ScrollView, which you will implement next.

9781484204375_Fig04-37.jpg

Figure 4-37. UIWebView in the iPad Air emulator

ScrollView

ANDROID ANALOGY

android.widget.ScrollView.

Due to the smaller screen size on mobile devices, ScrollView is very useful for displaying a content view larger than the physical display. To implement this in iOS, you use UIScrollView.

To make UIScrollView work with auto layout, it is easier to wrap all the widgets in a container view first, which will let you lay out the widgets in the container view as you normally would with auto layout.

The following steps demonstrate how you would normally do this with iOS UIScrollView in the CommonWidgets app:

  1. Select all widgets in the root View, select Embed In image View from the Xcode menu bar to embed all these common widgets in a View (see Figure 4-38), and change this storyboard label to containerView.

    9781484204375_Fig04-38.jpg

    Figure 4-38. Embed all widgets in a View

  2. Change View Controller Simulated Size to Fixed. The storyboard scene becomes shorter, with some widgets left out of the screen as shown in Figure 4-39.

    9781484204375_Fig04-39.jpg

    Figure 4-39. Change scene to Fixed size

  3. With the container, you can scroll the containerView as a whole by embedding the containerView in UIScrollView. Select containerView first and embed it in a scroll view as shown in Figure 4-40 (Menubar image Editor image Embed In image Scroll View).

    9781484204375_Fig04-40.jpg

    Figure 4-40. Embed containerView in a scroll view

  4. Open the Add New Constraints popup and pin the UIScrollView edges to the edge of the super view with zero spacing, and update the Frame (see Figure 4-41).

    9781484204375_Fig04-41.jpg

    Figure 4-41. Set the UIScrollView position

  5. The preceding operation shifts the contentView. In contentView Size Inspector, reposition the contentView at (0,0) with size 600 (do not change the height).
  6. The auto layout constraints are repurposed for calculating the scrolling position. You have to create auto layout constraints to pin the edges to the parent UIScrollView with zero spacing (see Figure 4-42).

    9781484204375_Fig04-42.jpg

    Figure 4-42. contentView pinned to UIScrollView with zero spacing

  7. To makes the containerView adaptive to size classes, create constraints that align the widget to the parent’s leading and trailing edges. Currently, you cannot use storyboard to draw auto layout for this, so you have to write code as shown in Listing 4-20.

    Listing 4-20. Pin contentView Edges to Screen/Root View Edges

    class ViewController: ... {
      ...
      @IBOutlet var mContainer: UIView!
      override func viewDidLoad() {
      ...
      var leftConstraint = NSLayoutConstraint(
                item: self. mContainer,
           attribute: NSLayoutAttribute.Leading,
           relatedBy: NSLayoutRelation(rawValue: 0)!,
              toItem: self.view,
           attribute: NSLayoutAttribute.Leading,
          multiplier: 1.0,
            constant: 0)
        self.view.addConstraint(leftConstraint)

        var rightConstraint = NSLayoutConstraint(
                item: self. mContainer,
           attribute: NSLayoutAttribute.Trailing,
           relatedBy: NSLayoutRelation(rawValue: 0)!,
              toItem: self.view,
           attribute: NSLayoutAttribute.Trailing,
          multiplier: 1.0,
            constant: 0)
        self.view.addConstraint(rightConstraint)

      }
      ...
  8. You can also set the other widgets for auto layout so they adapt to different size classes.

Build and run the app to see iOS UIScrollView in action. You should be able to see all the widgets by scrolling the view up and down (see Figure 4-43).

9781484204375_Fig04-43.jpg

Figure 4-43. CommonWidgets with scrollable view

Animations

Back in the old days, I cared a lot less about animation effects, but I think the evolution of iOS definitely raised the bar. In iOS, you can animate UIView properties using the simple UIView animation API.

To learn by example, modify the existing UISegmentedControl.doScValueChanged(...) method as shown in Listing 4-21 to create some animation effects using the UIView.animateWithDuration(...) method.

Listing 4-21. UIView.animateWithDuration(...)

...
@IBAction func doScValueChanged(sender: AnyObject) {
  var idx = self.mSegmentedControl.selectedSegmentIndex
  self.logText("segment (idx)")
  let center = self.mButton.center
  UIView.animateWithDuration(1, animations: { action in
    self.mButton.center = CGPoint(x: center.x, y: center.y / (idx + 1))
    self.mButton.alpha = 1 / (idx + 1)
    }, completion: { action in
      UIView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.CurveEaseInOut, animations: { action in
        self.mButton.center = center
        }, completion: {  action in
          // do nothing
        })
    })
}
...

The UIView.animateWithDuration(...) has several overloaded variants that all work the same way. You can animate the following UIView properties by modifying them inside the animation block:

  • frame for the viewing area,
  • center for position,
  • transform for scale and rotation,
  • alpha for transparency, and
  • backgroundColor.

Save Data

Saving data is an essential programming task in almost all common programming platforms. In addition to transactional data, most of the mobile apps also save application states and user preferences so that users can resume their tasks later. Both iOS and Android provide several persistent storage options. The iOS SDK offers the following choices:

  • User Defaults System storage
  • File storage
  • CoreData framework and SQLite database (not covered in this book)

Before diving into the first two options, you will create an Xcode project so that you can write code and visualize how these options work.

  1. Launch Xcode, use the Single View Application template, and name the project SaveData.
  2. Create a Navigation Bar with an UIBarButtonItem (see Figure 4-44):
    1. Select the View Controller in storyboard, and from the Xcode menu bar, select Editor image Embed In image Navigation Controller.
    2. In the Navigation Item Attributes Inspector, enter a Title of SaveData.
    3. Drag a BarButtonItem from Object Library and drop it onto the Navigation Item in the View Controller scene. Update the Bar Item Title in Attributes Inspector to be Delete.
    4. Select the Bar Button Item and open Assistant Editor to connect the selector outlet to your code. Name the IBActiondoDelete.

      9781484204375_Fig04-44.jpg

      Figure 4-44. SaveData project storyboard

  3. Create a UITextField to get user input as shown in Figure 4-44:
    1. Drag a UITextField onto the View Controller scene. In the Attributes Inspector, enter the placeholder, "Please save my inputs !!!".
    2. Add auto layout constraints to center the UITextField in the View.
  4. Open Assistant Editor, and in the UITextField Connections Inspector connect the following outlets to your code:
    1. Connect Referencing Outlet to IBOutlet property, mTextField.
    2. Connect delegate to your ViewController class.
  5. Implement UITextFieldDelegate protocol in the ViewController class and create the stubs that will triggers the retrieve, save, and delete code as shown in Listing 4-22:
    1. Load the saved data in ViewController.viewDidLoad().
    2. Save the user input when the Return key is pressed.
    3. Give users a choice to delete any persistent data they created.

Listing 4-22. SaveData ViewController

class ViewController: UIViewController, UITextFieldDelegate {
  ...
  let STORAGE_KEY = "key"
  @IBOutlet weak var mTextField: UITextField!
  override func viewDidLoad() {
    ...
    self.mTextField.text = self.retrieveUserInput()
  }

  @IBAction func doDelete(sender: AnyObject) {
    self.deleteUserInput();
  }

  func textFieldShouldReturn(textField: UITextField!) -> Bool {
    ...
    self.saveUserInput(self.mTextField.text)
    return true
  }

  func saveUserInput(str: String) {
    // TODO
  }

  func retrieveUserInput() -> String? {
    // TODO
    return nil
  }

  func deleteUserInput() {
    // TODO
  }
  ...

Nothing is new yet. Build and run the SaveData project (see Figure 4-45).

9781484204375_Fig04-45.jpg

Figure 4-45. The SaveData project display

Without completing the method stubs, this new project exhibits a common problem that can be observed in the following three steps:

  1. Enter something in the input text field.
  2. Exit the app.
  3. Relaunch the app. The previous input is gone!

For typical app settings or user preferences, users are not happy if they need to reenter them every time they launch the app. The app needs to save the data and load it back when the app restarts.

NSUserDefaults

ANDROID ANALOGY

SharedPreferences.

Just like Android’s SharedPreferences, which saves key-value pairs of primitive data types, you use NSUserDefaults class to interface with iOS’s User Defaults system for the same purpose. NSUserDefaults takes care of data caching and syncing for developers. It is easy to use and its performance is already optimized.

The values to be managed in the iOS defaults system can be primitives or the so-called property list object (e.g., NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary). For NSArray and NSDictionary objects, their contents must be property list objects as well.

Continue with the SaveData project. You are going to fix the problem you just observed.

  1. Create the convenient methods that save, retrieve, and delete data using the NSUserDefaults API (see Listing 4-23):
    1. Get the NSUserDefaults object.
    2. You may accumulate multiple updates and call synchronize() to send the batched updates to the defaults system storage.

    Listing 4-23. Save, Retrieve, and Delete in defaults system

    class ViewController: UIViewController, UITextFieldDelegate {
      ...
      let userDefaults = NSUserDefaults.standardUserDefaults()
      func saveUserdefault(data: AnyObject, forKey: String) {
        userDefaults.setObject(data, forKey: forKey)
        userDefaults.synchronize()
      }

      func retrieveUserdefault(key: String) -> String? {
        var obj = userDefaults.stringForKey(key)
        return obj
      }

      func deleteUserDefault(key: String) {
        self.userDefaults.removeObjectForKey(key)
      }
      ...
  2. Earlier, you already created the stubs that are wired to the right events. Call the convenient methods just created to complete the persistent code (see Listing 4-24).

Listing 4-24. Save, Retrieve, and Delete using the UserDefaults system

class ViewController: UIViewController, UITextFieldDelegate {
  ...
  func saveUserInput(str: String) {
    self.saveUserdefault(str, forKey: STORAGE_KEY)
  }

  func retrieveUserInput() -> String? {
    return self.retrieveUserdefault(STORAGE_KEY)
  }

  func deleteUserInput() {
    self.deleteUserDefault(STORAGE_KEY)
  }
  ...

Relaunch the SaveData project and repeat the failed test case. You should no longer need to reenter the name when reopening the app.

File Storage

Just like Java, the iOS SDK provides system APIs to interface with the file system. In iOS, you commonly use the following API:

  • The NSFileManager class.
  • The NSString, NSArray, NSDictionary, and NSData Foundation classes also have convenient methods to store and retrieve themselves from file systems.

NSFileManager

ANDROID ANALOGY

java.io.File.

If you need to perform any file-related tasks to manipulate File and Directory, the NSFileManager class provides the API to do the work. You need to specify the file path or file URL for the destination file and specify the NSData object for the file contents.

To show and to learn by example, use NSFileManager to achieve the same save/retrieve/delete purpose.

  1. Create the convenient methods that save, retrieve, and delete data using the NSFileManager API (see Listing 4-25):
    1. Get the NSFileManager object.
    2. Use NSHomeDirectory().stringByAppendingPathComponent(...) to build the iOS file path.
    3. NSFileManager deals with NSData, which can be converted to common Foundation data types (e.g., String, array and dictionary).

    Listing 4-25. Manage Data in files  using NSFileManager

    class ViewController: UIViewController, UITextFieldDelegate {
      ...
      let fileMgr = NSFileManager.defaultManager()
      func saveToFile(str: String, file: String) {
        var path = NSHomeDirectory().stringByAppendingPathComponent("Documents").stringByAppendingPathComponent(file)
        var data = str.dataUsingEncoding(NSUTF8StringEncoding)
        var ok = fileMgr.createFileAtPath(path, contents: data, attributes: nil)
      }

      func retrieveFromFile(file: String) -> String? {
        var path = NSHomeDirectory().stringByAppendingPathComponent("Documents").stringByAppendingPathComponent(file)
        var data = fileMgr.contentsAtPath(path)
        var str = NSString(data:data, encoding: NSUTF8StringEncoding)

        return str
      }

      func deleteFile(file: String) {
        var path = NSHomeDirectory().stringByAppendingPathComponent("Documents").stringByAppendingPathComponent(file)
        var ok = fileMgr.removeItemAtPath(path, error: nil)
      }
      ...

    Note  Each app can only write to certain folders inside the application home (e.g., the Documents folder). As in Android, the most common write error is probably trying to create a file in the wrong place.

  2. Call the convenient methods just created to complete the persistent code that uses NSFileManager (see Listing 4-26).

Listing 4-26. Save, Retrieve, and Delete in File using NSFileManager

class ViewController: UIViewController, UITextFieldDelegate {
  ...
  func saveUserInput(str: String) {
//    self.saveUserdefault(str, forKey: STORAGE_KEY)
    self.saveToFile(str, file: STORAGE_KEY)
  }

  func retrieveUserInput() -> String? {
//    return self.retrieveUserdefault(STORAGE_KEY)
    return self.retrieveFromFile(STORAGE_KEY)
  }

  func deleteUserInput() {
//    self.deleteUserDefault(STORAGE_KEY)
    self.deleteFile(STORAGE_KEY)
  }
  ...

Many Foundation data types contains convenient methods to interface with File for saving and retrieving themselves. Listing 4-27 depicts the code that saves and retrieves the string itself:

Listing 4-27. Save String using Foundation Class API

  func saveToFile(str: String, file: String) {
    var path = ...
    var error: NSError?
    str.writeToFile(path, atomically: true, encoding:
NSUTF8StringEncoding, error: &error)

  }

  func retrieveFromFile(file: String) -> String? {
    var path = ...
    var error: NSError?
    var str = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)

    return str
  }

You can find the same writeToFile(...) and constructor(...) methods in NSDictionary, NSArray, and NSData for saving and retrieve themselves as well. Just for a quick exercise, Listing 4-28 serves the same purpose as Listing 4-27:

Listing 4-28. Save String using Foundation Class API instead of using the NSFileManager API

let KEY_JSON = "aKey"
func saveJsonToFile(str: String, file: String) {
  var path = ...
  // one entry dict, for sure can have more
  var ser = NSDictionary(objects: [str], forKeys: [KEY_JSON])
  ser.writeToFile(path, atomically: true)
}

func retrieveJsonFromFile(file: String) -> String? {
  var path = ...
  var ser = NSDictionary(contentsOfFile: path)!
  return ser[KEY_JSON] as String?
}

This is particularly useful when dealing with JSON messages because most remote messages are in JSON format nowadays.

Generally, you only need to use NSFileManager directly for pure file-system operations like inspecting file attributes, or iterating thru files in directories.

Networking and Using Remote Service

A typical client-server solution hosts information on the server side, while client apps either fetch data from the server and present it to users in meaningful ways, or collect data from the users to submit to the server. You probably hear the buzzwords “mobile commerce” or “m-commerce” a lot nowadays. To describe them in simple terms, mobile apps fetch product items from a server and then submit the purchase orders to the server via the Internet. From a mobile-apps programming perspective, this is really not new at all. It is still a client-server programming topic using HTTP GET/POST, which is what most of the e-commerce web sites do.

I will talk about JSON messages and RESTful services for mobile apps specifically because of their popularity versus traditional SOAP-based web services.

Perform Network Operations in Background

For apps with a user interface, you want to perform I/O tasks or network-related code in the background, and do UI updates in the UI main thread. Otherwise, the app appears to the user to lag because the UI thread is blocked, waiting for the task to finish. This principle applies to iOS, Android, and probably any UI platforms. The Android SDK provides the convenient android.os.AsyncTask class to perform tasks in a background thread and hook back to the UI main thread when the background task is completed. Generally, when interfacing with a remote server, you want to fetch data in the background thread. When the remote data is received, your UI code presents the data on the screen.

To show how to achieve the same objectives in iOS, you will create a simple iOS app, as shown in Figure 4-46, to demonstrate some basic RESTFul client code that consumes remote RESTFul services:

  • When the GET or POST button is selected, the app sends HTTP GET or POST to the server in a background thread.
  • When the HTTP response is received, the app renders the data on the user interface.

9781484204375_Fig04-46.jpg

Figure 4-46. The iOS RestClient app

Create a new Xcode project to have a fresh start.

  1. Launch Xcode, use the Single View Application template, and name the project RestClient.
  2. Draw your storyboard with the following widgets (see Figure 4-47):
    1. A UIButton to invoke HTTP GET
    2. A UIButton to invoke HTTP POST
    3. A UITextField to take user input
    4. A UIWebView to render the HTTP response

    9781484204375_Fig04-47.jpg

    Figure 4-47. RestClient storyboard

  3. Connect storyboard outlets to your code (see Listing 4-29):
    1. Connect the GET button Touch Down event to your doGet()IBAction method.
    2. Connect the POST button Touch Down event to your doPost()IBAction method.
    3. Connect the UITextField delegate outlet to ViewController class.
    4. Connect the UIWebView delegate to ViewController class.
    5. Connect the text field New Referencing Outlet to the ViewController mTextField IBOutlet property
    6. Connect the webview New Referencing Outlet to the ViewController mWebView IBOutlet property

Listing 4-29. RestClient Preparation Code

class ViewController: UIViewController, UITextFieldDelegate, UIWebViewDelegate {

  @IBOutlet weak var mWebView: UIWebView!
  @IBOutlet weak var mTextField: UITextField!

  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view ...
  }

  func textFieldShouldReturn(textField: UITextField!) -> Bool {
    textField.resignFirstResponder();
    return true
  }

  @IBAction func doGet(sender: AnyObject) {
  }

  @IBAction func doPost(sender: AnyObject) {
  }
}

Nothing is new yet, just the repeated storyboard tasks and the process of connecting the outlets to your code with the method stubs. You will fill the main topics in these stubs next.

RESTFul Service using HTTP

Most of the RESTFul services support HTTP/HTTPS protocols. Since the RESTFul services are supposed to be agnostic to client apps, it is not surprising that your Android code most likely can be translated nicely to the iOS platform if it consumes the same RESTFul services. To retrieve data from most RESTFul services, it is similar to how browsers use HTTP GET to fetch a remote HTML file. You can use HTTP GET to fetch an HTML document from mobile apps, too—or it can fetch any data, such as raw bytes, an XML or JSON document, and so forth.

To submit user input, you will very often use HTML Form to submit form data from an HTML page to HTTP servers. The form data is transmitted using the HTTP POST method. This is very common in iOS and Android apps as well. Technically speaking, you can also send a query string to an HTTP server using the HTTP GET method, just as some web pages do. In this case, you can simply build the URL with query strings and use the HTTP GET method to send data to your server. It is a design decision that you will make by understanding usages and conventions of GET versus POST. The key is to design the interface so both your mobile clients and the server can understand it.

NSURLConnection

ANDROID ANALOGY

HttpURLConnection.

To interface with HTTP protocol in iOS, you can use the NSURLConnection class to send GET and POST URL requests. The API is fairly similar to Android’s HttpUrlConnection.

Continue with the RestClient project and add code to send HTTP requests by doing the following:

  1. Implement the IBAction doGet() method to send the HTTP GET request and to get data from the HTTP response (see Listing 4-30):
    1. Create an NSMutableURLRequest object.

      Note  You commonly escape/encode the URL path or query string just like you normally do using URLEncoder in Android.

    2. Set HTTP method to GET.

      Note  An HTTP method is case-sensitive according to HTTP protocol specs.

    3. Set the accept header, which is commonly used for content negotiation (e.g., text/html, json/application, etc.).

      Note  Our sample echo service supports "text/html", "text/plain", and "application/json" content types. To demonstrate the content negotiation visually, I choose to use a UIWebView widget to render the server response and specify the "text/html". In general, "application/json" is more suitable for data exchange.

    4. NSURLConnection.sendAsynchronousRequest sends asynchronous HTTP Request and receives the HTTP response in the completionHandler closure in the UI main thread.

      Listing 4-30. HTTP GET

      let URL_TEST = "http://pdachoice.com/ras/service/echo/"
      @IBAction func doGet(sender: AnyObject) {
        var text = self.mTextField.text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLPathAllowedCharacterSet())
        var url = URL_TEST + text
        var urlRequest = NSMutableURLRequest(URL: NSURL(string: url)!)
          urlRequest.HTTPMethod = "GET"
          urlRequest.setValue("text/html", forHTTPHeaderField: "accept")

        NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(),
          completionHandler: {(resp: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
            self.mWebView.loadData(data, MIMEType: resp.MIMEType, textEncodingName: resp.textEncodingName, baseURL: nil)
          })
        }
  2. Implement the IBAction doPost() method to send HTTP POST to post data to the server and receive an HTTP response (see Listing 4-31). Almost the same as sending HTTP GET, you use NSURLConnection.sendAsynchronousRequest to send asynchronous HTTP messages, except you set the HTTP method to POST:
    1. Make sure set the HTTP method is set to POST.
    2. POST data has the same format as a query string, but you want to encode it to put in the HTTP Body, the same way you do in Android.
    3. To parse JSON content or create a JSON object, NSJSONSerialization is your friend. You want to convert the JSON object to NSDictionary or the JSON array to NSArray.

Listing 4-31. HTTP POST

@IBAction func doPost(sender: AnyObject) {
  var text = self.mTextField.text.stringByAddingPercentEncodingWithAllowedCharacters(
       NSCharacterSet.URLQueryAllowedCharacterSet())
  var queryString = "echo=" + text;
  var formData = queryString.dataUsingEncoding(NSUTF8StringEncoding)!
  var urlRequest = NSMutableURLRequest(URL: NSURL(string: URL_TEST)!)
  urlRequest.HTTPMethod = "POST"
  urlRequest.HTTPBody = formData

  urlRequest.setValue("application/json", forHTTPHeaderField: "accept")

  NSURLConnection.sendAsynchronousRequest(urlRequest,
    queue: NSOperationQueue.mainQueue(),
    completionHandler: {(resp: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
      println(resp.MIMEType)
      println(NSString(data: data, encoding: NSUTF8StringEncoding))

      var json = NSJSONSerialization.JSONObjectWithData(data, options:
          NSJSONReadingOptions.AllowFragments, error: nil) as NSDictionary
      self.mWebView.loadHTMLString(json["echo"] as String, baseURL: nil)
    })
})
}

Note  SERVER_URL = "http://pdachoice.com/ras/service/echo" is a simple web service that echoes back the path parameter. Desktop browsers are fully capable of rendering plain text as well as HTML documents. You can use a desktop browser to verify the data from the server.

Build and run the RestClient project and enter "Hi you!" to see the live app in action. The simple echo service with the GET method actually responded in HTML format, <html><body><h1>Hi you!</h1></body></html>, which is rendered as shown in Figure 4-48.

9781484204375_Fig04-48.jpg

Figure 4-48. RestClient doGet and doPost responses

Summary

This chapter introduced the most common programming component mappings from Android to iOS:

  • User interface and UI widgets
  • Persistent storage options
  • Network and remote services with JSON

Many meaningful apps only ever deal with the components discussed herein. This chapter listed all the viable mappings with step-by-step instructions on how to translate Android components to their iOS counterparts. You will see how to apply these guidelines to build a simple but complete utility app from start to finish next.

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

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