Chapter    4

Implement Piece by Piece

In the previous chapter, you used an iOS storyboard to lay the groundwork for using navigation patterns. It resulted in a set of connected UIViewController classes in a model-view-controller (MVC) framework that mapped to Android counterpart fragments.

In this chapter, you will implement each view-controller pair one piece at a time. 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 JavaScript Object Notation (JSON)

User Interface

All those storyboard scenes that you implemented using the screen navigation patterns in Chapter 3 were intentionally simple. Obviously, a useful mobile app provides rich content and offers better functionality to gracefully interact with users. The user interface (UI) 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 most of the front-end and back-end web development platforms. UI components are normally platform dependent. You just need to know the application programming interface (API) usages of the user interface widgets and where to look up the platform-specific widget specifications.

iOS content views and storyboard scenes are structured in a view container (view-parent) model, which has existed in the industry for a long time. In iOS, UIView is an object that draws content and UI widgets 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.

To position UI widgets in the content view, iOS uses Auto Layout to set the location and size of the widgets within each one’s parent view or relative to a sibling.

 Note  There might be some similarities between the iOS UI framework and HTML mock-up structures. However, I think the amount of similarity is not enough to help you to understand the iOS UIKit framework. I strongly suggest you have an open mind and learn the object-oriented style of the iOS UI framework from scratch without trying to force your current HTML and web approaches into this subject.

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.

WEB ANALOGY

In HTML, there is no equivalent for a UI base class, but an HTML element like <div> has few default attributes but its look and behavior can be customized in infinite ways.

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 the UIView object that have attributes for the intended look and feel and behaviors. When drawing the view elements in a 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’s Attributes Inspector. For example, Figure 4-1 depicts the View section in the Attributes Inspector for UI widgets.

9781484209325_Fig04-01.jpg

Figure 4-1. View section of the Attributes Inspector

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

  • 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 now. Before diving into common iOS UI widgets from the iOS software development kit (SDK), I will talk about an important related topic, application resources, which are used by the UI widgets as well as many other common programming tasks.

Application Resources

WEB ANALOGY

Web applications can use images located anywhere, but organized projects will store these assets in logical locations.

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 section will demonstrate how to implement two common use cases in Xcode: using the assets catalog and externalizing strings.

Using the Assets Catalog

You can group images for different device configurations in the assets catalog. This simplifies the management of the images for different screen sizes and device configurations when using them in your iOS code. This section will show you how to do this in iOS. Do the following:

  1. Launch Xcode, use the Single View Application template, and name the project CommonWidgets. The project comes with one assets 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.

    9781484209325_Fig04-02.jpg

    Figure 4-2. AppIcon set in Images.xcassets

  2. Create images for the iOS app icon with the different image resolutions specified in the editor. Drag the appropriate image files to the guided squares. Figure 4-3 depicts the result.

    9781484209325_Fig04-03.jpg

    Figure 4-3. Re-creating icons for iOS with Images.xcassets

  3. To add a regular 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 can choose to supply the image set by selecting the Universal size class or other device-specific types. Either way, 1x, 2x, and 3x should cover all the iOS devices now.
    3. Select the image with 1x resolution and drag it to the right spot (as shown by the pointer under the sample in Figure 4-4). Repeat the step for the 2x and 3x images.

      9781484209325_Fig04-04.jpg

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

    4. Give the image set a name (for example, type sample). The name is the identifier to access the image from your code or from a storyboard.

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 your storyboard. You will 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.

Externalizing Strings

Generally, you store string text in external files. Both Android and iOS read externalized strings in a similar manner. In iOS, they are stored in "key" = "value"; format in .strings files. In your Swift code, instead of hard-coding the values directly, use the system API to get the string values by key. This is similar to the method for localizing web applications for international audiences. To externalize strings for an iOS project, do the following:

  1. Create a new file anywhere in your Xcode project. For example, to create a new file in the Supporting Files folder, press flower.jpg+N (the keyboard shortcut for New File).
    1. On the “Choose a template” screen, select iOS image Resource image Strings File.
    2. Save the file as Localizable.strings. This is the default file name used by the iOS API. You can create multiple .strings files and specify the file name using the iOS system API.
  2. Copy Listing 4-1 into your iOS Localizable.strings to start with. You will use these strings later.

    Listing 4-1. iOS Localizable.strings

    "app_name" = "HelloAndroid";
    "action_settings" = "Settings";
    "hello_world" = "Hello world!";
    "hello_button" = "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 the 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 internationalization. Although I will not cover localization/internationalization in depth, the concept and process are similar in many programming platforms.

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 to display information to users.

The iOS UIKit framework provides rich system UI widgets that you “draw” in the 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 web UI components when appropriate. Continuing with the CommonWidgets project 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 the storyboard, and in the Size Inspector, change Simulated Size to Freeform. Make the size 320x1500 (Figure 4-5) to give the view enough height to start with.

9781484209325_Fig04-05.jpg

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

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

 Note  Don’t bother implementing 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 the scroll view.

UILabel

WEB ANALOGY

<h1> to <h6> or <p> elements are commonly used in HTML pages.

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 by following these steps:

  1. Select Main.storyboard and drag a Label from the 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.

    9781484209325_Fig04-06.jpg

    Figure 4-6. Adjusting UILabel size and position

  2. Update the UILabel attributes in the Attributes Inspector, as shown in Figure 4-7.
    1. For Text, enter My simple text label.
    2. For Alignment, use center alignment.
    3. The other options (Shadow, Autoshrink, and so on) are all safe to play with, too.

      9781484209325_Fig04-07.jpg

      Figure 4-7. Updating UILabel attributes

  3. Open the Assistant Editor and connect IBOutlet in the Connections Inspector to your code so that you can update UILabel programmatically. Most of the attributes in the 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 the UILabel in action (Figure 4-8).

9781484209325_Fig04-08.jpg

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

UITextField

WEB ANALOGY

<input> elements are commonly used in HTML pages.

In iOS, UITextField accepts a single line of user input and shows 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 TextField from the Object Library to the root view, as shown in Figure 4-9. Position the UITextField right under the UILabel.

    9781484209325_Fig04-09.jpg

    Figure 4-9. Changing the UITextField size and position

  2. Update its attributes in the Attributes Inspector as shown in Figure 4-10.
    1. For Placeholder, enter “Hint: one-line text input.”
    2. Fill in the other attributes as shown in Figure 4-10.

      9781484209325_Fig04-10.jpg

      Figure 4-10. Defining UITextField in the Attributes Inspector

  3. Open the Assistant Editor and connect the following outlets in the 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 the UITextFieldDelegate protocol in ViewController. Listing 4-4 shows the common way to dismiss the keyboard when the Return key is pressed.

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

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

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

UITextView

WEB ANALOGY

<textarea> elements are commonly used in HTML pages.

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 the Object Library to the root view, as shown in Figure 4-11. Position the widget right under the UITextField.

    9781484209325_Fig04-11.jpg

    Figure 4-11. Changing the UITextView size and position

  2. Update its attributes in the Attributes Inspector:
    1. For Text, enter “multiple lines.”
    2. Take a look at its Attributes Inspector. Many attributes are similar to the UITextField but not exactly (for example, there is no Placeholder attribute).
  3. Open the Assistant Editor and connect IBOutlet in the 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. UITextView 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 View.resignFirstResponder() to dismiss the keyboard.

UIButton

ANDROID ANALOGY

<button> or <input type="button"> form elements are commonly used in HTML pages.

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 the Object Library to the root view, and position the UIButton right under the text view, as shown in Figure 4-12.
  2. Update its attributes in the Attributes Inspector (see Figure 4-12).
    1. Most of the Button attributes are associated with the button states. Set the State Config attribute first to Default.
    2. Set Title to Action Button.
    3. Set Image to sample.
    4. The other settings are all safe to play with. You may accept the defaults, as shown in Figure 4-12.

      9781484209325_Fig04-12.jpg

      Figure 4-12. UIButton position and attributes

  3. Open the Assistant Editor and connect IBOutlet and IBAction in the 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 clicked, it simply logs the “Button clicked” text in the UITextView (see Figure 4-13).

9781484209325_Fig04-13.jpg

Figure 4-13. Button clicked in UITextView

UISegmentedControl

ANDROID ANALOGY

<select /> elements are commonly used in HTML pages.

In iOS, UISegmentedControl offers closely related but mutually exclusive choices. To show and learn by example, do the following in the CommonWidgets app:

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

    9781484209325_Fig04-14.jpg

    Figure 4-14. UISegmentedControl size and position

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

      9781484209325_Fig04-15.jpg

      Figure 4-15. UISegmentedControl attributes

  3. Open the Assistant Editor and connect IBOutlet and IBAction in the 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 UISegmentedControl in action. Each segment has a zero-based index (see Figure 4-16).

9781484209325_Fig04-16.jpg

Figure 4-16. UISegmentedControl zero-based index

UISlider

WEB ANALOGY

<input type="range"> elements are commonly used in HTML pages.

iOS’s 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 demonstrate the iOS UISlider, do the following:

  1. Select Main.storyboard, drag a UISlider from the Object Library, and place it below the UISegmentedControl object, as shown in Figure 4-17.
  2. Update its attributes in the Attributes Inspector (see Figure 4-17):
    1. Set the Min value to 0 and the max to 100.
    2. Min Image and Max Image.
    3. Leave Min Track Tint and Max Track Tint at the defaults.
    4. Disable Continuous Updates.

      9781484209325_Fig04-17.jpg

      Figure 4-17. Updating the UISlider attributes

  3. Open the Assistant Editor and connect IBOutlet and IBAction to your code in the 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 UISlider in action. As you move the slider by dragging the circle on the slider, called thumb, its value continues to be printed in the UITextView (see Figure 4-18).

9781484209325_Fig04-18.jpg

Figure 4-18. UISlider value updates

UIActivityIndicatorView

WEB ANALOGY

This is not a built-in HTML element, but it is commonly implemented in front-end web pages using JavaScript or CSS.

UIActivityIndicatorView displays a “busy” activity indicator for a task or something else in progress. You may commonly call it a spinner, loader, or wait cursor in your web app. To learn and visualize this iOS UI widget, do the following in the CommonWidgets iOS app:

  1. Select Main.storyboard, drag a UIActivityIndicatorView from the Object Library, and position it left-aligned and below the UISlider (see Figure 4-19).
  2. Update its attribute in the Attributes Inspector, as shown in Figure 4-19:
    1. Style: Set this to Gray.
    2. Color: Set this to Default.
    3. Behavior: Both Animating and Hides When Stopped are commonly enabled.

      9781484209325_Fig04-19.jpg

      Figure 4-19. UIActivityIndicatorView attributes

  3. Open the Assistant Editor and connect IBOutlet in the 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

HTML <progress> elements (new in HTML 5) are commonly used in HTML pages.

To show a task with a 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 learn and visualize how the iOS UIProgressView works, do the following:

  1. Select Main.storyboard, drag a UIProgressView from the Object Library, and position it below and left-aligned to the activity indicator, as shown in Figure 4-20.
  2. Update its attributes in the 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

      9781484209325_Fig04-20.jpg

      Figure 4-20. UIActivityIndicatorView attributes

  3. Open the Assistant Editor and connect IBOutlet in the Connections Inspector to your code so that you can update UIProgressView programmatically. Modify the UISlider delegate method, doSliderValueChanged(...) as shown in Listing 4-10.

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

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

9781484209325_Fig04-21.jpg

Figure 4-21. UIProgressView update in action

UISwitch

WEB ANALOGY

<input type="checkbox"> elements are commonly used in HTML pages.

The switch-like widgets are user friendly for presenting mutually exclusive choices. In web pages, you may find widgets with many different styles for the same purpose. 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 the Object Library. Position it to the right of UIActivityIndicatorView (see Figure 4-22).
  2. Update its attribute in the Attributes Inspector (see Figure 4-22).
    1. Set State to On.
    2. You can change any other attributes safely.

      9781484209325_Fig04-22.jpg

      Figure 4-22. UIActivityIndicatorView

  3. Open the Assistant Editor and connect IBOutlet and IBAction in the 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).

9781484209325_Fig04-23.jpg

Figure 4-23. iOS UISwitch look and feel

UIImageView

WEB ANALOGY

HTML <img> elements are commonly used in HTML pages.

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 a 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. In pdf, set Type to Vectors in the Attributes Inspector.
    3. Drag a PDF file to the universal slot, as shown in Figure 4-22. There is no need to provide 2x or 3x images.

      9781484209325_Fig04-24.jpg

      Figure 4-24. Creating an image set

  2. Select Main.storyboard, drag a UIImageView object from the Object Library to the view, and position it under the UIProgressView object, as shown in Figure 4-25.

    9781484209325_Fig04-25.jpg

    Figure 4-25. UIImageView attributes

  3. Update its attributes in the Attributes Inspector:
    1. Set Image to pdf.
    2. Set Mode to Aspect Fit.
    3. Set the others as shown in Figure 4-23.
  4. Open the Assistant Editor and connect IBOutlet in the Connections Inspector to your code. Listing 4-12 demonstrates 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 few attributes you need to master. However, when it comes to creating a UIImage and optimizing its size and performance, you want to look into the UIImage class to see how you would construct the UIImage instance from various sources. There are actually iOS frameworks that primarily deal with images, like Quartz 2D or OpenGL. If you know WebGL, 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 rich graphics API that will support you for iOS graphics-editing tasks.

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

9781484209325_Fig04-26.jpg

Figure 4-26. UIImageView in iPhone 5

Menu

Menus are used to provide quick access to frequently used actions. They are particularly common in desktop, Android, and web apps. Although there is no such similarly named feature in the iOS SDK, UIBarButtonItem in UIToolbar or UINavigationBar serves a similar purpose: quick access.

UIBarButtonItem

WEB ANALOGY

This is usually implemented with an add-on UI widget library.

For quick access actions 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 can create a bottom bar, UIToolbar, if all of the buttons in UIBarButtonItem don’t fit on the top 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 the Object Library to the view and position it on top of the view. Frequently, you will just select the View Controller to embed it in a NavigationController in the storyboard (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. Multiselect all the widgets in the scene and reposition them to make room for the top bar.
    2. Update the Navigation Item attributes in the Attributes Inspector (for example, enter CommonWidgets for Title).

      9781484209325_Fig04-27.jpg

      Figure 4-27. Navigation Controller and Navigation Bar

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

    9781484209325_Fig04-28.jpg

    Figure 4-28. UIBarButtonItem attributes

  3. Open the Assistant Editor and connect 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

WEB ANALOGY

This is an add-on modal window widget.

In web apps, you can use a modal callout to prompt users to make a selection. The operations and the look and feel (L&F) establish a strong relationship to the context that originates the operations. On the iPad, you can safely choose UIPopoverController (see Chapter 3) to present the list of selections, which on the iPhone is automatically presented full-screen.

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

The key SDK class is UIAlertController, which was introduced in Chapter 3 for alert dialogs (see Listing 3-22). To learn the iOS UIActionSheet 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 can use Title or Message to establish a visual connection to the originating context.
  3. It is common to have a destructive UIAlertAction in red for a delete or remove action, which is specified with the UIAlertActionStyle.Destructive style.
  4. UIActionSheet is presented as a pop-over on the iPad. You need to specify location information or the barButtonItem.

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
    // 4. for iPAD support
    if let popoverController = actionSheet.popoverPresentationController {
      popoverController.barButtonItem = sender as UIBarButtonItem
    }

    self.presentViewController(actionSheet, animated: true, completion: nil)
  }
  ...

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

9781484209325_Fig04-29.jpg

Figure 4-29. UIAlertController with UIActionSheet style

UIPickerView

WEB ANALOGY

HTML <select> elements are commonly used in HTML pages.

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 for this purpose, except with one trivial difference: drop-downs show only the selected value while the other choices are not shown after selected.

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

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

    9781484209325_Fig04-30.jpg

    Figure 4-30. Placing the UIPickerView

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

      9781484209325_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 the 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’s UIPickerView in action. The iPhone emulator is too small for all the widgets you have so far. You need a browser-like scroll bar (which you will implement soon). You can run it in the iPad emulator for now (see Figure 4-32).

9781484209325_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 information 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.

9781484209325_Fig04-33.jpg

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

Play Video

WEB ANALOGY

This is equivalent to the <video> element in HTML 5 or the Flash video player.

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 steps (see Listing 4-16) demonstrate the simplest usage:

  1. Implement the useMoviePlayerViewController() method, which plays a video in MPMoviePlayerViewController (see Listing 4-16).
    1. Create an instance with a URL link to a remote video source. iOS supports the HTTP Live Streaming (HLS) protocol. 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 web apps, 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 UIActionSheet (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-full-screen mode, use MPMoviePlayerController directly to play video in a UIView widget:
    1. Select Main.storyboard, drag a UIView from the Object Library, and position it below the UIPickerView, as shown in Figure 4-34. This is the viewing area that shows the video.

      9781484209325_Fig04-34.jpg

      Figure 4-34. View element for the video

    2. Open the Assistant Editor to connect IBOutlet in the Connections Inspector to your code’s 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 browser-like scroll view yet, but you can run it in the iPad emulator for now (see Figure 4-35).

9781484209325_Fig04-35.jpg

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

UIWebView

WEB ANALOGY

HTML <iframe> elements are commonly used in HTML pages..

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 supports many HTML5 and CSS3 features (for example, offline cache and web sockets, and so on).

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 the Attributes Inspector; it is commonly set to Scales Page To Fit.

    9781484209325_Fig04-36.jpg

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

  2. As usual, open the Assistant Editor and connect the following outlets in the 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 the needful, like re-direct or intercept etc.
    return true; // false to stop http request
  }
  func webViewDidStartLoad(webView: UIWebView) {
    // do the needful, i.e., start UIActivityViewIndicator
    self.mActivityIndicator.startAnimating()
  }
  func webViewDidFinishLoad(webView: UIWebView) {
    // do the needful, i.e., 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 a widget that scrolls content just like any browser does with HTML pages; you will implement this next.

9781484209325_Fig04-37.jpg

Figure 4-37. UIWebView in the iPad Air emulator

ScrollView

Because of the smaller screen size on mobile devices, ScrollView is 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’s UIScrollView in the CommonWidgets app:

  1. Using a single child view will simplify scrolling all the widgets in it. 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.

    9781484209325_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.

    9781484209325_Fig04-39.jpg

    Figure 4-39. Change the scene to a fixed size

  3. With the containerView that contains all the widgets, 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 (Editor image Embed In image Scroll View).

    9781484209325_Fig04-40.jpg

    Figure 4-40. Embed containerView in a scroll view

  4. Open the Add New Constraints pop-up and pin the UIScrollView edges to the edge of the super view with zero spacing; then update the frame (see Figure 4-41).

    9781484209325_Fig04-41.jpg

    Figure 4-41. Set the UIScrollView constraint

  5. The preceding operation shifts the containerView. In the containerView Size Inspector, reposition the containerView to (0,0) with a size of 600 (do not change the height).
  6. ScrollView needs to know the content size, either explicitly or calculated from Auto Layout if you set up your constraints the right way. To make it adaptive to a size class, the next step is to add constraints from the content view to the scroll view:
    1. The Auto Layout constraints are repurposed for calculating the scrolling content size. You have to create Auto Layout constraints to pin the edges to the parent UIScrollView with zero spacing (see Figure 4-42).

      9781484209325_Fig04-42.jpg

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

  7. The content view width is not adaptive yet because the leading and trailing Auto Layout constraints to the scroll view created in the preceding step were repurposed for the scroll view contentSize. You can create the same constraints to views outside of the scroll view, that is, the root view.
    1. Connect the containerView view element in storyboard scene to your Swift code: the @IBOutlet mContainerView property.
    2. Currently, you cannot use a storyboard to add constraints to a parent’s parent. 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 mContainerView: UIView!
        override func viewDidLoad() {
        ...
          var leftConstraint = NSLayoutConstraint(
            item: self.mContainerView,
            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.mContainerView,
            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 widgets inside the containerView 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).

9781484209325_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 has 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.mSegmentedController.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 / CGFloat(idx + 1))
    self.mButton.alpha = 1 / CGFloat(idx + 1)
    }, completion: { action in
      UIView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .CurveEaseInOut,
        animations: { action in
          self.mButton.center = center
        }, completion: {  action in
          // do nothing
      })
  })
}
...

The UIView.animateWithDuration(...) method 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
  • backgroundColor

Note, you cannot animate the hidden property directly. You would need to animate the alpha value to animate the fade-out or fade-in effects.

Save Data

WEB ANALOGY

Traditionally, a web app’s front end is more of a thin client approach that saves data on the server side. You may find the topic more familiar to back-end implementations. Even in the front-end JavaScript development, the HTML5 local storage is good to store a small amount of user preferences as well.

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. Most of the native including desktop, iOS, and other mobile platforms provide several persistent storage options. The iOS SDK offers the following choices:

  • User Defaults System
  • File storage
  • Core Data 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 the Object Library and drop it onto the Navigation Item in the View Controller scene. Update the Bar Item Title in the Attributes Inspector to Delete.
    4. Select the Bar Button Item and open the Assistant Editor to connect the selector outlet to your code. Name the IBActiondoDelete.
  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.

      9781484209325_Fig04-44.jpg

      Figure 4-44. SaveData project storyboard

  4. Open the Assistant Editor; in the UITextField Connections Inspector, connect the following outlets to your code:
    1. Connect Referencing Outlet to the IBOutlet property, mTextField.
    2. Connect delegate to your ViewController class.
  5. Implement the UITextFieldDelegate protocol in the ViewController class and create the stubs that will trigger 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).

9781484209325_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 when the app restarts.

NSUserDefaults

You typically store a small amount of nonsensitive user preferences or application settings so that your users don’t need to reenter the setting all the time in web apps. You can store this data in a server database, in HTML5 browser local storage, or in cookies. In iOS, you use the 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 User Defaults System can be primitives or the so-called property list object (for example, 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 will 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 want to accumulate multiple updates and call synchronize() to send the batched updates to the User Defaults System storage.

      Listing 4-23. Save, Retrieve, and Delete in User 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 User Defaults 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

WEB ANALOGY

This is commonly available for back-end server development but not available for front-end web development.

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

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

NSFileManager

If you need to perform any file-related tasks to manipulate File and Directory, NSFileManager 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.

       Note  Each app can write only to certain a certain folder inside the application home (for example, the Documents folder). The most common error is probably trying to create a file in the wrong place.

    3. NSFileManager deals with NSData, which can be converted to common Foundation data types (for example, 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)
          if let data = fileMgr.contentsAtPath(path) {
            return NSString(data:data, encoding: NSUTF8StringEncoding)
          }
          
          return nil
        }
        
        func deleteFile(file: String) {
          var path = NSHomeDirectory().stringByAppendingPathComponent("Documents").stringByAppendingPathComponent(file)
          var ok = fileMgr.removeItemAtPath(path, error: nil)
        }
        ...
  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 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)
  }
  ...

Generally, you only need to use NSFileManager directly for pure file-system operations such as inspecting file attributes or iterating through files in directories because iOS Foundation data types contain convenient methods to interface with File for saving and retrieving themselves. Listing 4-27 depicts simpler 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 init(...) methods in NSDictionary, NSArray, and NSData for saving and retrieving themselves. Just as a quick exercise, Listing 4-28 serves the same purpose as Listing 4-27.

Listing 4-28. Save NSDictionary Using Foundation Class API

  let KEY_JSON = "aKey"
  func saveJsonToFile(str: String, file: String) {
    var path = NSHomeDirectory().stringByAppendingPathComponent("Documents").stringByAppendingPathComponent(file)
    // one enry dict, for sure can be more
    var json = NSDictionary(objects: [str], forKeys: [KEY_JSON])
    json.writeToFile(path, atomically: true)
  }
  
  func retrieveJsonFromFile(file: String) -> String? {
    var path = NSHomeDirectory().stringByAppendingPathComponent    ("Documents").stringByAppendingPathComponent(file)
    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.

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 have heard the buzzwords mobile commerce or m-commerce a lot recently. 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 e-commerce 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. 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 in the UI thread.

To show how to achieve the objectives in iOS by example, 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 an 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.

9781484209325_Fig04-46.jpg

Figure 4-46. The iOS RestClient app

Create a new Xcode project.

  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 an HTTP GET
    2. A UIButton to invoke an HTTP POST
    3. A UITextField to take user input
    4. A UIWebView to render the HTTP response

      9781484209325_Fig04-47.jpg

      Figure 4-47. RestClient storyboard

  3. Connect the following 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 the ViewController class.
    4. Connect the UIWebView delegate to the 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; you just used the same storyboard tasks and the same 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 the HTTP and HTTPS protocols. To retrieve data from a RESTful services, use HTTP GET to fetch a remote HTML file. You can use an 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 often use an 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 common in iOS 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 the 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

To interface with the HTTP protocol in iOS, you can use the NSURLConnection class to send GET and POST URL requests. The API is fairly similar to JAVA’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.

    2. Set the HTTP method to GET.

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

    3. Set the accept header, which is commonly used for content negotiation (for example, text/html, json/application, and so on).

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

    4. NSURLConnection.sendAsynchronousRequest sends an 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) {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = true
        var text = self.mTextField.text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
          
        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
            UIApplication.sharedApplication().networkActivityIndicatorVisible = false
            println(resp.textEncodingName)
            self.mWebView.loadData(data, MIMEType: resp.MIMEType, textEncodingName: resp.textEncodingName, baseURL: nil)
        })
      }
  2. Implement the IBAction doPost() method to send an 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 to set the HTTP method 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) {
  UIApplication.sharedApplication().networkActivityIndicatorVisible = true
  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
      UIApplication.sharedApplication().networkActivityIndicatorVisible = false

      println(NSString(data: data, encoding: NSUTF8StringEncoding))
        
      var json = NSJSONSerialization.JSONObjectWithData(data, options:
        NSJSONReadingOptions.AllowFragments, error: nil) as NSDictionary
      var echo = json["echo"] as String
      self.mWebView.loadHTMLString(echo, 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 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.

9781484209325_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 simple components discussed in this chapter. You will see how to apply these guidelines to build a simple but complete utility app from start to finish in the next chapter.

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

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