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:
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.
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:
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:
Figure 4-2. AppIcon set in Images.xcassets
Figure 4-3. Re-creating icons for iOS with Images.xcassets
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 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:
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";
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:
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:
Figure 4-6. Adjusting UILabel size and position
Figure 4-7. Updating UILabel attributes
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).
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:
Figure 4-9. Changing the UITextField size and position
Figure 4-10. Defining UITextField in the Attributes Inspector
Note Other methods are defined in UITextFieldDelegate. -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:
Figure 4-11. Changing the UITextView size and position
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:
Figure 4-12. UIButton position and attributes
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).
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:
Figure 4-14. UISegmentedControl size and position
Figure 4-15. UISegmentedControl attributes
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).
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:
Figure 4-17. Updating the UISlider attributes
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).
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:
Figure 4-19. UIActivityIndicatorView attributes
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:
Figure 4-20. UIActivityIndicatorView attributes
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).
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:
Figure 4-22. UIActivityIndicatorView
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).
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:
Figure 4-24. Creating an image set
Figure 4-25. UIImageView attributes
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.
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:
Figure 4-27. Navigation Controller and Navigation Bar
Figure 4-28. UIBarButtonItem attributes
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.
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).
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:
Figure 4-30. Placing the UIPickerView
Figure 4-31. Connecting the UIPickerView outlets
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).
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.
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:
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)
}
...
Figure 4-34. View element for the video
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).
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:
Figure 4-36. iOS UIWebView delegate in the Connections Inspector
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)
}
...
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.
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:
Figure 4-38. Embed all widgets in a View
Figure 4-39. Change the scene to a fixed size
Figure 4-40. Embed containerView in a scroll view
Figure 4-41. Set the UIScrollView constraint
Figure 4-42. contentView pinned to UIScrollView with zero spacing
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)
...
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).
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:
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:
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.
Figure 4-44. SaveData project storyboard
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).
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:
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.
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)
}
...
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:
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.
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.
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)
}
...
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.
Figure 4-46. The iOS RestClient app
Create a new Xcode project.
Figure 4-47. RestClient storyboard
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:
Note You commonly escape/encode the URL path or query string.
Note An HTTP method is case-sensitive according to the HTTP protocol specs.
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.
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)
})
}
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.
Figure 4-48. RestClient doGet and doPost responses
Summary
This chapter introduced the most common programming component mappings from Android to iOS.
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.