Spin a Web - Learn iOS 8 App Development, Second Edition (2014)

Learn iOS 8 App Development, Second Edition (2014)

Chapter 3. Spin a Web

Warm up your coding fingers. This chapter will introduce you to some of the core skills of iOS app development, along with a healthy dose of Swift code. The app you’ll create in this chapter and the steps you’ll take are typical of the way iOS apps are built. From that perspective, this will be your first “real” iOS app.

You’ve already learned to use Interface Builder to add library objects to your app, customize them, and connect them. In this chapter, you will do the following:

· Customize a Swift class

· Add outlets and actions to your custom class using Swift

· Connect those outlets to objects using Interface Builder

· Connect objects to your custom actions using Interface Builder

· Alter the behavior of a library object by connecting it to a delegate

The app you’re going to build is a URL shortening app. This app relies on one of the many URL shortening services available. These services take a URL of any length and generate a much shorter URL, which is far more convenient to read, recite over the phone, or use in a tweet. A URL shortening service works by remembering the original URL. When anyone in the world attempts to load the web page at the short URL, the service returns a redirect response, directing the browser to the original URL.

To make this app, you’ll learn how to embed a web browser in it—a trick that has many uses. It will also show you how to programmatically send and receive an HTTP request from your app, a useful tool for creating apps that use Internet services.

Note To computer programmers, the word programmatically means “by writing computer code.” It means you accomplished something by writing instructions in a computer language, such as Swift, as opposed to any other way. As an example, Interface Builder will let you connect two objects by dragging a line between those objects. You can write Swift code to connect those same two objects. If you used the latter method, you could say that you “set the connection programmatically.”

Design

This app needs some basic elements. The user will need a field to type in and edit a URL. It would be nice to have a built-in web browser so they can see the page at that URL and tap links to go to other URLs. It needs a button to convert the long URL into a short one and some place to display the shortened URL.

That’s not a particularly complicated design, and everything should easily fit on one screen, like the sketch in Figure 3-1. Let’s toss in one extra feature: a button to copy the shortened URL to the iOS clipboard. Now the user has an easy way to paste the shortened URL into another app.

image

Figure 3-1. Sketch of Shorty app

Your app will run on all iOS devices and work in both portrait and landscape orientations. Now that you have a basic design, it’s time to launch Xcode and get started.

Creating the Project

As with any app, start by creating a new project in Xcode. This is a one-screen app, so the obvious choice is the Single View Application template.

Fill in the project details, as shown in Figure 3-2. Name the project Shorty, set the language to Swift, and choose Universal for devices.

image

Figure 3-2. Shorty project details

Click the Next button. Choose a location to save your new project and click Save.

Building a Web Browser

Start by building the web browser portion of your app. This will consist of a text field, where the user enters the URL they want to visit/convert, and a web view that will display that page. Let’s also throw in a refresh button to reload that page at the current URL.

Select the Main.storyboard file in the navigator. In the object library, find the navigation bar object and drag it into the view, toward the top, as shown in Figure 3-3. Navigation bar objects are normally created by navigation controllers to display a title, a back button, and so on. You saw this in the Surrealist app. Here, however, you’re going to use one on its own.

image

Figure 3-3. Adding a navigation bar

Position the navigation bar so it’s the full width of the view. Make sure it’s still selected and click the pin constraints control (the second of four) in the lower-right corner of the canvas. In the pin constraints pop-up, click to set the top, left, and right edge constraints, as shown in Figure 3-4.

image

Figure 3-4. Adding constraints to the navigation bar

Make sure the value for all three constraints is 0. This tells iOS to position the navigation bar at the recommended spot at the top of the screen, just below the system’s status bar, and it will be the full width of the display. Click the Add 3 Constraints button. Click the Resolve Auto Layout Issues control (the third of four), as shown on the right in Figure 3-4, and choose the Update Frames command. This will reposition the view in Interface Builder based on the constraints you just set.

Now add the web view. Find the Web View object in the library and drag one into the lower portion of the screen. Move and resize the web view so it exactly fills the rest of the view, from the navigation bar to the bottom of the screen, as shown in Figure 3-5.

image

Figure 3-5. Adding a web view

Click the Resolve Auto Layout Issues control again, but this time choose Add Missing Constraints in View Controller. Interface Builder uses the constraints you’ve already established and fills in any additional constraints needed to establish this layout for all devices.

Find the bar button item object in the library and drag one into the right side of the navigation bar, as shown in Figure 3-6. Bar button items are button objects specifically designed to be placed in a navigation bar or toolbar.

image

Figure 3-6. Adding a button to the navigation bar

Once there, select it. Switch to the attributes inspector and change the Identifier setting of the new button to Refresh (see Figure 3-7). The icon of the button will change to a circular arrow.

image

Figure 3-7. Making a refresh button in the navigation bar

Find the text field (not the text view!) object in the library and drag one into the middle of the navigation bar. This object will displace the default title that is normally displayed. Grab the resize handle on the right or left and stretch the field so it fills most of the free space in the navigation bar, as shown in Figure 3-8.

image

Figure 3-8. Resizing the URL field

The user will type their URL into this field. Configure it so it is optimized for entering and editing URLs. Select the new text field object and, using the attributes inspector, change the following properties:

· Set text to http://.

· Set Placeholder Text to http://.

· Change Clear Button to Appears while editing.

· Change Correction to No.

· Change Keyboard Type to URL.

· Change Return Key to Go.

These settings set the initial content of the field to http:// (so the user doesn’t have to type that), and if they clear the field, a ghostly http:// will prompt them to enter a web URL. Turning spelling correction off is appropriate (URLs are not a spoken language). When the keyboard appears, it will be optimized for URL entry, and the Return key on the keyboard will display the word Go, indicating that the URL will load when they tap it.

You’ve created and laid out all of the visual elements of your web browser. Now you need to write a little code to connect those pieces and make them work together.

Coding a Web Browser

Select the ViewController.swift file in the project navigator (see Figure 3-9). This file defines your ViewController class. This is a custom class, which you created—well, technically, it was created on your behalf by the project template, but you can take credit for it. I won’t tell anyone. The job of your ViewController object is to add functionality to and manage the interactions of the view objects it’s connected to. Your app has only one view, so you need only one view controller.

image

Figure 3-9. Adding properties to ViewController.swift

Different objects have different roles to play in your app. These roles are explained in Chapter 8. When you add code to your app, you need to decide what class to add it to. This app is simple; you’ll add all of your customizations to the ViewController class.

Tip Are the terms class and object confusing? Read the first part of Chapter 6 for an explanation.

Your ViewController class is a subclass of the UIViewController class, which is defined by the Cocoa Touch framework. This means your ViewController class inherits all of the features and behavior of a UIViewController—which is a lot becauseUIViewController is quite sophisticated. If you did nothing else, your ViewController objects would behave exactly like any other UIViewController object.

The fun is in editing ViewController.swift to add new features or change the behavior it inherited.

Adding Outlets to ViewController

Start by adding two new properties to ViewController. A property defines a value associated with an object. In its simplest form, it merely creates a new variable that the object will remember. Add these properties to ViewController.swift (new code in bold):

class ViewController: UIViewController {
@IBOutlet var urlField: UITextField!
@IBOutlet var webView: UIWebView!

When you’re done, your class definition should look like the one in Figure 3-9. So, what does all this mean? Let’s examine these declarations in detail:

· @IBOutlet is an important keyword that makes this property appear as an outlet in Interface Builder.

· var defines a variable property.

· urlField/webView is the name of the property.

· UITextField/UIWebView is the type of the property. In this case, it’s the class of the object this property stores.

· The ! means it’s an optional variable, but it is assumed to contain a valid object reference. Optionals are explained in Chapter 20.

By adding these properties to ViewController, you enable any ViewController object to be directly connected to one text field object (via its urlField property) and one web view object (via its webView property).

You’ve defined the potential for being connected to two other objects, but you haven’t connected them. For that, you’ll use Interface Builder.

Connecting Custom Outlets

Click the Main.storyboard file in the project navigator. Find and select the view controller object in the outline or in the dock above the view, both shown in Figure 3-10.

image

Figure 3-10. Selecting the view controller object for a scene

In most cases, a screen in iOS is defined by a single view controller object and a collection of view objects. When it’s time to display that screen, iOS reads the information from the .storyboard file and uses that to construct new instances of all the objects in that scene. In addition to creating the objects, it also connects the objects, using the outlets and connections you set. When it’s done, your view controller and all of its view objects will have been created, initialized, and connected.

Note Don’t worry if you don’t get this concept right away. Interface Builder is elegant and simple, but it takes most people a while to fully grasp how it works. Check out Chapter 15 for an in-depth explanation of how Interface Builder works its magic.

So far, you’ve added the objects to the scene, but you’ve yet to connect them. Do that now. Select the view controller object and show the connections inspector. In it, you’ll see the urlField and webView properties you just added to ViewController.swift. These appear in Interface Builder because you included the @IBOutlet keyword in your property declarations.

Drag the connection circle to the right of urlField and connect it to the text field in the navigation bar, as shown in Figure 3-11. Now, when the ViewController scene is loaded, the urlField property of your view controller object will refer to the text field in your interface. Pretty cool, huh?

image

Figure 3-11. Connecting an outlet to an object

Another handy way of setting connections is to Control+drag or right-click+drag from the object with the connection to the object you want it connected to. Holding down the Control key, click the view controller object (either in the sidebar or at the top of the scene) and drag down to the web view object, as shown in Figure 3-12.

image

Figure 3-12. Connecting the web view outlet

When you release the mouse button, a pop-up menu will appear asking you to choose which outlet to set. Choose webView.

Adding Actions to ViewController

Why did you do all of this (creating outlets and connecting them in Interface Builder)? Your controller object needs to get the value of the URL typed by the user and communicate that to the web view object so the web view knows what URL to load. Your view controller object is acting as a liaison or manager, getting data from one object (the text field) and assigning tasks to another (the web view). Do you see now why it’s called a controller?

It’s a simple task, but there has to be some code that will make it happen. Select the ViewController.swift file in the project navigator. After the existing functions included by the project template and just before the closing } of the class declaration, add this new function:

func loadLocation( AnyObject ) {
var urlText = urlField.text

if !urlText.hasPrefix("http:") && !urlText.hasPrefix("https:") {
if !urlText.hasPrefix("//") {
urlText = "//" + urlText
}
urlText = "http:" + urlText
}

let url = NSURL(string: urlText)
webView.loadRequest(NSURLRequest(URL: url))
}

This function does one simple task: it loads the web page at the URL entered by the user. This will require three basic steps.

1. Get the string of characters the user typed into the text field.

2. Convert that string of characters into a URL object.

3. Request that the web view object load the page at that URL.

Here’s the breakdown of this code:

var urlText = urlField.text

The first line declares a new string object variable, named urlText, and assigns it the value of the text property of the urlField property of this object. The urlField property is the one you just added to this class. Your urlField refers to the UITextField object in your interface because you connected it in Interface Builder. A UITextField object has a text property that contains the characters currently in the field—either ones the user typed or those you put there programmatically. (See, I used the word programmatically again).

Tip To see the documentation for any class or constant, hold down the Option key and single-click (quick view) or double-click (full documentation) its name. To see the properties and functions of the UITextField class, hold down the Option key and double-click the wordUITextField in the .swift file.

The first part of your task is already accomplished; you’ve retrieved the text of the URL using the urlField property you defined and connected. The next few lines might look a little strange.

if !urlText.hasPrefix("http:") && !urlText.hasPrefix("https:") {
if !urlText.hasPrefix("//") {
urlText = "//" + urlText
}
urlText = "http:" + urlText
}

If you’re comfortable with Swift, take a close look at this code. It isn’t critical to your app; you could leave it out, and your app would still work. It does, however, perform a kindness for your users. It checks to see whether the string the user typed starts with http:// or https://, the standard protocols for a web page. If these standard URL elements are missing, this code inserts one automatically.

Computers tend to be literal, but you want your app to be forgiving and friendly. The previous code allows the user the type in just www.apple.com (for example), instead of the correct http://www.apple.com, and the page will still load. Does that make sense? Let’s move on.

Object-oriented programming is all about encapsulating the complexity of things in objects. While a string object can represent the characters of a URL, it’s still just a string (an array of characters). Most functions that work with URLs expect a URL object. In Cocoa Touch, the class of URL objects is NSURL. How do you turn the String you got from the text field into an NSURL you can use with the web view? I thought you’d never ask.

let url = NSURL(string: urlText)

This line of code creates a new URL object from a string object. In Swift, you create a new object using the class of the object (NSURL) followed by a set of parentheses (()), just as if you were calling a function. This creates a new instance of the class and executes its initializer. A class can declare a variety of ways in which it can be created, and the NSURL class provides an initializer that creates an NSURL object directly from a String object. As you can see, it’s pretty easy to convert a string object into a URL object, and there are functions that convert the other way too, which you’ll use later in this chapter.

With the second step accomplished, the last thing left to do is display the web page at that URL in the web view. That’s accomplished with the last line in your function.

webView.loadRequest(NSURLRequest(URL: url))

webView is the webView property you created earlier, and it’s connected to the web view object on the screen. You call that object’s loadRequest() function to load the page. It turns out, however, that a web view needs a URL request (NSURLRequest) object, not just a simple URL object. A URL request not only represents a URL but also describes how that URL should be transmitted over the network. For your purposes, a plain-vanilla HTTP GET request is all you need, and the expression NSURLRequest(URL:url) creates a new NSURLRequest object from the given URL, which you pass on to loadRequest(_:). The rest of the work is done by the web view.

Setting Action Connections

Let’s review what you’ve accomplished so far.

· You created a text field object where the user can type in a URL.

· You created a web view object that will display the web page at that URL.

· You added two outlets (properties) to your ViewController class.

· You connected the text field and web view objects to those outlets.

· You wrote a loadLocation(_:) function that takes the URL in the text view and loads it in the web view.

What’s missing? The question is “How does the loadLocation(_:) function get called?” That’s a really important question, and, at the moment, the answer is “never.” The next, and final, step is to connect the loadLocation(_:) function to something so it runs and loads the web page.

Start by adding the @IBAction keyword to your function. In your ViewController.swift file, locate the func loadLocation() declaration and edit it so it looks like this:

@IBAction func loadLocation( AnyObject ) {

The @IBAction keyword tells Interface Builder that this function can be called by an interface object, just as the @IBOutlet keyword told Interface Builder that the property could refer to an interface object. A function that can be invoked by objects (such as buttons and text fields) in your interface is called an action.

Select the Main.storyboard file again. Select the text field object and switch to the connections inspector. Scroll down until you find Did End On Exit in the Sent Events section. Drag the connection circle to the View Controller object and release the mouse, as shown inFigure 3-13. A pop-up menu will ask you what action you want this event connected to; choose loadLocation: (which is currently the only action).

image

Figure 3-13. Setting the Did End On Exit action connection

You also want the web page loaded when the user taps the refresh button, so connect the refresh button to the same action. The refresh button is simpler than the text field and sends only one kind of event (“I was tapped”). Use an Interface Builder shortcut to connect it. Hold down the Control key, click the refresh button, and drag the connection to the View Controller object. Release the mouse button and select the loadLocation: sent action, as shown in Figure 3-14.

image

Figure 3-14. Setting the action for the refresh button

Testing the Web Browser

Are you excited? You should be. You just wrote a web browser app for iOS! Make sure the build destination is set to an iPhone simulator (see Figure 3-15) and click the Run button.

image

Figure 3-15. Setting the iPhone simulator destination

Your app will build and launch in the iPhone simulator, as shown on the left in Figure 3-16. Tap the text field, and a URL-optimized keyboard appears. Tap out a URL (I’m using www.apple.com for this example) and tap the Go button. The keyboard retracts, and Apple’s home page appears in the web view. That’s pretty darn nifty.

image

Figure 3-16. Testing your web browser

So, how does it work? The text field object fires a variety of events, depending on what’s happening to it. You connected the Did End On Exit event to your loadLocation(_:) function. This event is sent when the user “ends” editing by tapping the action button in the keyboard (Go). When you ran the app and tapped Go, the text field triggered its Did End On Exit event, which called your ViewController’s loadLocation(_:) function. Your function got the URL the user typed in and told the web view to load it. Voilá! The web page appears.

Note The iOS simulator uses your computer’s Internet connection to emulate the device’s Wi-Fi or cellular data connection. If you’re working through this chapter on a desert island, your app might not work.

Debugging the Web View

What you’ve developed so far is pretty impressive. Go ahead—try any web page, I’ll wait. Only two things about it bother me. First, when you tap a link in the page, the URL in the text field doesn’t change. Second, the web pages are crazy big.

The second problem is easy to fix. Quit the simulator or switch back to Xcode and click the Stop button in the toolbar. Select the web view object in the Main.storyboard file and switch to the attributes inspector, as shown in Figure 3-17. Find and check the Scale Page to Fit option. Now, when the web view loads a page, it will zoom the page so you can see the whole thing.

image

Figure 3-17. Setting the Scale Page to Fit property

The first problem is a little trickier to solve and requires some more code. I’ll address that one as you add the rest of the functionality to your app.

Adding URL Shortening

You now have an app that lets you enter a URL and browse that URL in a web browser. The next step, and the whole purpose of this app, is to convert the long URL of that page into a short one.

To accomplish that, you’ll create and lay out new visual objects in Interface Builder, create outlets and actions in your controller class, and connect those outlets and actions to the visual object, just as you did in the first part of this chapter. If you haven’t guessed by now, this is the fundamental app development workflow: design an interface, write code, and connect the two.

Start by fleshing out the rest of the interface. Edit Main.storyboard, select the web view object, grab its bottom resizing handle, and drag it up to make room for some new view objects at the bottom of the screen, as shown in Figure 3-18. Select the vertical constraint beneath the view (also shown in Figure 3-18) and delete it (press the Delete key or choose Edit image Delete). You no longer want the bottom edge of the web view to be at the bottom edge of its superview; you now want it to snuggle up to the toolbar view, which you’ll add in a moment.

image

Figure 3-18. Making room for new views

In the library, find the toolbar object (not a navigation bar object, which looks similar) and drag it into the view, as shown in Figure 3-19. Position it so it fits snugly at the bottom of the view.

image

Figure 3-19. Adding a toolbar

Find the bar button item object in the library and add new button objects to the toolbar, as shown in Figure 3-20, until you have three buttons.

image

Figure 3-20. Adding additional button objects to the toolbar

You’re going to customize the look of the three buttons to prepare them for their roles in your app. The left button will become the “shorten URL” action, the middle one will be used to display the shortened URL, and the right one will become the “copy short URL to clipboard” action. Switch to the attributes inspector and make these changes:

1. Select the leftmost button.

a. Change Identifier to Play.

b. Uncheck Enabled.

2. Select the middle button.

a. Set Style to Plain.

b. Change Title to Tap arrow to shorten.

c. Change Tint to Black Color.

3. Select the rightmost button.

a. Change Title to Copy.

b. Uncheck Enabled.

Now select and resize the web view so it touches the new toolbar. Finish the layout by choosing Add Missing Constraints in View Controller from the Resolve Auto Layout Issues button. The final layout should look like Figure 3-21.

image

Figure 3-21. Finished interface

Just like before, you’ll need to add three outlets to the ViewController class so your object has access to these three buttons. Select the ViewController.swift file in the project navigator and add these three declarations immediately after your existing outlets:

@IBOutlet var shortenButton: UIBarButtonItem!
@IBOutlet var shortLabel: UIBarButtonItem!
@IBOutlet var clipboardButton: UIBarButtonItem!

Select the Main.storyboard Interface Builder file, select the view controller object, and switch to the connections inspector. The three new outlets will appear in the inspector. Connect the shortenButton outlet to the left button, the shortLabel outlet to the middle button, and theclipboardButton to the right button, as shown in Figure 3-22.

image

Figure 3-22. Connecting outlets to toolbar buttons

Designing the URL Shortening Code

With your interface finished, it’s time to roll up your sleeves and write the code that will make this work. Here’s how you want your app to behave:

1. The user enters a URL into the text field and taps Go. The web view loads the web page at that URL and displays it.

2. When the page is successfully loaded, two things happen.

a. The URL field is updated to reflect the actual URL loaded.

b. The “shorten URL” button is enabled, allowing the user to tap it.

3. When the user taps the “shorten URL” button, a request is sent to the URL shortening service.

4. When the URL shortening service sends its response, two things happen:

a. The shortened URL is displayed in the toolbar.

b. The “copy to clipboard” button is enabled, allowing the user to tap it.

5. When the user taps on the “copy to clipboard” button, the short URL is copied to the iOS clipboard.

You can already see how most of this is going to work. The “shorten URL” and “copy to clipboard” button objects will be connected to actions that perform those functions. The outlets you just created will allow your code to alter their state, such as enabling the buttons when they’re ready.

The pieces in between these steps are a little more mysterious. The “When the page is successfully loaded” makes sense, but how does your app learn when the web page has loaded or whether it was successful? The same is true with the “when the URL shortening service sends its response.” When does that happen? The answer to these questions is found in multitasking and delegates.

“Multi-what?” you ask. Multitasking is doing more than one thing at a time. Usually, the code you write does one thing at a time and doesn’t perform the next thing until the first is finished. There are, however, techniques that enable your app to trigger a block of code that will execute in parallel so that both blocks of code are running, more or less, concurrently. This is explained in more detail in Chapter 24. You’ve already done this in your app, probably without realizing it.

webView.loadRequest(NSURLRequest(URL: url))

The loadRequest(_:) function of the web view object doesn’t load the URL; it simply starts the process of loading the URL. The call to this function returns immediately, and your code continues, doing other things. This is called an asynchronous function. One of those things you want to keep doing is responding to user touches—something that’s covered in Chapter 4. This is important because it keeps your app responsive.

Meanwhile, code that’s part of the UIWebView class started running on its own, quietly sending requests to a web server, collecting and interpreting the responses, and ultimately displaying the rendered page in the web view. This is often referred to as a background thread or background task because it does its work silently, and independently, of your main app (called the foreground thread).

Becoming a Web View Delegate

All of this multitasking theory is great to know, but it still doesn’t answer the question of how your app learns when a web page has, or has not, loaded. Tasks can communicate with one another in several ways. One of those ways is to use a delegate. A delegate is an object that agrees to undertake certain decisions or tasks for another object or would like to be notified when certain events occur. It’s this last aspect of delegates that you’ll use in this app.

The web view class has a delegate outlet. You connect that to the object that’s going to be its delegate. Delegates are a popular programming pattern in iOS. If you poke around the Cocoa Touch library, you’ll see that a lot of classes have a delegate outlet. Chapter 6 covers delegates in some detail.

Becoming a delegate is a three-step process.

1. In your custom class, adopt the delegate’s protocol.

2. Implement the appropriate protocol functions.

3. Connect the delegate outlet of the object to your delegate object.

A protocol is a contract, or promise, that your class will implement specific functions. This lets other objects know that your object has agreed to accept certain responsibilities. A protocol can declare two kinds of functions: required and optional. All required functions must be included in your class. If you leave any out, you’ve broken the contract, and your project won’t compile.

It’s up to you to decide which optional functions you implement. If you implement an optional function, that function will get called. If you don't, it won’t. It’s that simple. Most Cocoa Touch delegate functions are optional.

Tip A few older classes rely on what is called an informal protocol. It really isn’t a protocol at all but a documented set of functions that your delegate is expected to implement. The documentation for the class will explain which you should use. All of the steps for using an informal protocol are the same, except that there’s no formal protocol name to add to your class.

The first step is to decide what object will act as the delegate and adopt the appropriate protocol. Select your ViewController.swift file. Change the line that declares the class so it reads as follows:

class ViewController: UIViewController, UIWebViewDelegate {

The change is adding the UIWebViewDelegate to the end of the class declaration. Adding this to your class definition means that your class agrees to define the functions required by the UIWebViewDelegate protocol and is prepared to be connected to a UIWebView’s delegateoutlet.

Looking up the UIWebViewDelegate protocol, you find that it lists four functions, all of which are optional.

optional func webView(webView: UIWebView!, image
shouldStartLoadWithRequest request: NSURLRequest!, image
navigationType: UIWebViewNavigationType) -> Bool
optional func webViewDidStartLoad(webView: UIWebView!)
optional func webViewDidFinishLoad(webView: UIWebView!)
optional func webView(webView: UIWebView!, image
didFailLoadWithError error: NSError!)

The first function, webView(_:,shouldStartLoadWithRequest:...), is called whenever the user taps a link. It allows your delegate to decide whether that link should be taken. You could, for example, create a web browser that kept the user on a particular site, like a school calendar. Your delegate could block any link that took the user to another site or maybe just warn them that they were leaving. This app doesn’t need to do anything like that, so just ignore this function. By omitting this function, the web view will let the user tap and follow any link they want.

The next three functions are the ones you’re interested in. webViewDidStartLoad(_:) is called when a web page begins to load. webViewDidFinishLoad(_:) is called when it’s finished. And finally,webView(_:,didFailLoadWithError:) is called if the page could not be loaded for some reason.

You want to implement all three of these functions. Get started with the first one. Select your ViewController.swift file and find a place to add this function:

func webViewDidStartLoad( UIWebView ) {
shortenButton.enabled = false
}

When a web page begins to load, this function will disable (by setting the enabled property to false) the button that shortens a URL. You do this simply so the short URL button can’t be triggered between pages, and also you’re not yet sure if the page can be loaded successfully. You’d like to limit the URL shortening to URLs you know are good.

After that function, add this one:

func webViewDidFinishLoad( UIWebView ) {
shortenButton.enabled = true
urlField.text = webView.request.URL.absoluteString
}

This function is invoked after the web page is finished loading. The first line uses the shortenButton outlet you created earlier to enable the “shorten URL” button. So, as soon as the web page loads, the button to convert it to a short URL becomes active.

The second line fixes up an issue I brought up earlier in the “Debugging” section. You want the URL in the text field at the top of the screen to reflect the page the user is looking at in the web view. This code keeps the two in sync. After a web page loads, this line digs into the webViewobject to find the URL that was actually loaded. The request property (an NSURLRequest) contains a URL property (an NSURL), which has a property named absoluteString. This property returns a plain string object that describes the loaded URL. In short, it turns a URL into a string, the reverse of what you did in loadLocation(_:). The only thing left to do is to assign it to the text property of the urlField object, and the new URL appears in the text field.

The last function is called only if the web page couldn’t be loaded. It is, ironically, the most complicated function because you want to take the time to tell the user why the page wasn’t loaded—instead of just making them guess. Here’s the code:

func webView( webView: UIWebView, didFailLoadWithError error: NSError! ) {
var message = "That page could not be loaded. " + image
error.localizedDescription
let alert = UIAlertController(title: "Could not load URL",
message: message,
preferredStyle: .Alert )
let okAction = UIAlertAction(title: "That's Sad",
style: .Default,
handler: nil)
alert.addAction(okAction)
presentViewController(alert, animated: true, completion: nil)
}

The first statement creates a message that says “That page could not be loaded…” and appends a description of the problem from the error object the web view passed to the function. The next few statements create an alert view—a pop-up dialog—presenting the message to the user.

You’ve now done everything you need to make your ViewController class a web view delegate, but it isn’t a delegate yet. The last step is to connect the web view to it. Select the Main.storyboard file. Holding down the Control key, drag from the web view object and connect it to the view controller object. When you release the mouse button, choose the delegate outlet, as shown in Figure 3-23.

image

Figure 3-23. Connecting the web view delegate

Now your view controller object is the delegate for the web view. As the web view does its thing, your delegate receives calls about its progress. You can see this working in the simulator. Run your app, go to a URL (the example in Figure 3-24 uses http://developer.apple.com), and now follow a link or two in the web view. As each page loads, the URL in the text field is updated.

image

Figure 3-24. URL field following links

Tip Also try loading a URL or two that can’t be loaded by entering an invalid domain name or nonexistent path, as shown on the right in Figure 3-24. It’s important to test how your app handles failure too.

Shortening a URL

You’ve finally arrived at the moment of truth: writing the code to shorten the URL. But first, let’s review what has happened so far.

1. The user has entered a URL and loaded it into a web view.

2. When the web view loaded, your ViewController object’s webViewDidFinishLoad(_:) function was called, where your code enabled the “shorten URL” button.

What you want to happen next is for the user to tap the “shorten URL” button and have the long URL be magically converted into a short one. That sounds like an action. Select your ViewController.swift file again and add this new code:

let GoDaddyAccountKey = "0123456789abcdef0123456789abcdef"
var shortenURLConnection: NSURLConnection?
var shortURLData: NSMutableData?

@IBAction func shortenURL( AnyObject ) {
if let toShorten = webView.request.URL.absoluteString {
let encodedURL = toShorten.stringByAddingPercentEscapesUsingEncoding(image
NSUTF8StringEncoding)
let urlString = image
"http://api.x.co/Squeeze.svc/text/\(GoDaddyAccountKey)?url=\(encodedURL)"
shortURLData = NSMutableData()
let request = NSURLRequest(URL:NSURL(string:urlString))
shortenURLConnection = NSURLConnection(request:request, delegate:self)
shortenButton.enabled = false
}
}

The shortenURL(_:) action function sends a request to the X.co URL shortening service. iOS includes a number of classes that make complicated things—such as sending and receiving an HTTP request to a web server—relatively easy to write.

X.CO URL SHORTENING SERVICE

I chose to use the X.co URL shortening service in this project for several reasons. First, the service is free. Second, it has a well-documented and straightforward application programming interface (API) that can be used by performing a simple HTTP request. Finally, it has some debugging and management features. The service lets you log in and see what URLs your app has shortened, which is useful while you’re trying to debug it.

The X.co service is provided by GoDaddy!. To use X.co, go to the X.co web page and either create a free account or log in with your existing GoDaddy! account (if you’re already a customer). In your X.co account settings, you’ll find an account key—a 32-character hexadecimal string—that uniquely identifies you to the X.co service. This key must be included in your requests. Once you have your key, edit the following line in your ViewController class, replacing the dummy number between the quotes with your account key:

let GoDaddyAccountKey = "0123456789abcdef0123456789abcdef"

There are other URL shortening services out there, and you could easily adapt this app to use almost any of them. Some services, such as Bit.ly, even offer an iOS SDK that you can download and include in your project!

The X.co services will accept an HTTP GET request that includes the URL to be shortened and replies with a shortened URL. It’s that simple. A GET request is particularly easy to construct because all of the needed information is in the URL.

Writing shortenURL(_:)

Now let’s walk through shortenURL(_:) one line at a time. You begin by constructing the request URL. You’ll need three pieces of information.

· The service request URL

· Your GoDaddy! account key

· The long URL to shorten

The first piece of information is documented at the X.co web site. To convert a long URL into a short one and have the service return the shortened URL as plain text, submit a URL with this format:

http://api.x.co/Squeeze.svc/text/<YourAccountKey>?url=<LongURL>

To construct this URL, you’ll need the values for the two placeholders, <YourAccountKey> and <LongURL>. Get your account key from GoDaddy and use it to define the GoDaddyAccountKey value (see the “X.co URL Shortening Service” sidebar).

The last bit of information you need is the URL to shorten. Start with that, just as you did in the webViewDidFinishLoad(_:) function, and assign it to the toShorten variable.

if let toShorten = webView.request.URL.absoluteString {

The next two lines of code are the most complicated statements in your app. It constructs the entire X.co request URL using string interpolation—a fancy term for assembling a new string from other values.

let encodedURL = image
toShorten.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let urlString = image
"http://api.x.co/Squeeze.svc/text/\(GoDaddyAccountKey)?url=\(encodedURL)"

Notice that you don’t use the toShorten value directly. Instead, the stringByAddingPercentEscapesUsingEncoding(_:) function is used to replace any characters that have special meaning in a URL with a character sequence that won’t be confused for something important. The sidebar “URL String Encoding” explains why this is done and how it works. The result is kept in the encodedURL value.

URL STRING ENCODING

Computers, and thus computer programmers, deal with strings a lot. A string is a sequence of characters. Often, some characters in a string have special meaning. A URL can be represented as a string. Special characters separate the various parts of the URL. Here’s a generic URL with the special characters in bold:

scheme://some.domain.net/path?param1=value1&param2=value2#anchor

The colon, forward slash, question mark, ampersand, equals, and pound sign (hash) characters all have special meaning in a URL; they’re used to identify the various parts of the URL. All of the characters following the question mark are the query string portion of the URL. The ampersand character separates multiple name-value pairs. The fragment ID follows the pound sign character, and so on.

So, how do you write a URL that has a question mark character in the path or an ampersand character in one of the query string values? You can’t write the following; it won’t make any sense:

http://server.net/what?artcl?param=red&white

This is the problem you’re faced with when sending a URL to the X.co service. The query string of your URL contains another URL—it’s full of special characters, all of which have to be ignored. What you need is a way to write a character that normally has special meaning, without its special meaning. What you need is an escape sequence.

An escape sequence is a special sequence of characters used to represent a single character, so it’s treated like any other character, instead of something special. (Read that again until it makes sense.) URLs use the percent character (%) followed by two hexadecimal digits. When a URL sees a percent character followed by two hex digits, as in %63, it treats it as a single character, determined by the value of the two digits. Converting characters into escape sequences to preserve their values is called encoding a string.

The sequence %63 represents a single question mark character (?), and %38 means a single ampersand character (&). Now you can encode that pesky URL, and it will make sense to the recipient:

http://server.net/what%63artcl?param=red%38white

The stringByAddingPercentEscapesUsingEncoding(_:) function converts any characters that might be confusing to a URL and replaces them with escape sequences that mean the same character but without any special meaning. Now you have a string you can safely append to the query portion of the URL without confusing anyone, especially the server.

The next line constructs the entire URL (as a string) and stores it in the urlString value. The string is constructed using string interpolation. When you include the sequence \(any+expression) in a string literal, Swift replaces it with the value of the expression—assuming Swift knows how to convert results of the expression into a string. In your program, this is trivial because GoDaddyAccountKey and encodedURL are already strings, so the placeholders in the literal string are simply replaced with the actual strings.

This next line of code might seem like a bit of a mystery:

shortURLData = NSMutableData()

It sets an instance variable named shortURLData to a new, empty, NSMutableData object. Don’t worry about it now. It will make sense soon.

This next line of code is similar to what you used earlier to load a web page:

let request = NSURLRequest(URL: NSURL(string:urlString))

Just like the web view, the NSURLConnection class (the class that will send the URL for you) needs an NSURLRequest. The NSURLRequest needs an NSURL. Working backward, this line creates an NSURL from the URL string you just constructed and uses that to create a newNSURLRequest object, saving the final results in the request variable.

This next statement is what does (almost) all of the work:

shortenURLConnection = NSURLConnection(request:request, delegate:self)

Creating a new NSURLConnection object immediately starts the process of sending the requested URL. Just like the web view’s loadRequest(_:) function, this is an asynchronous function—it simply starts a background task and returns immediately. And just like the web view, you supply a delegate object that will receive calls about its progress, as they occur.

Unlike a web view, however, the delegate for an NSURLConnection is passed (programmatically) when you create the request. That’s what the delegate:self part of the call does; it tells NSURLConnection to use this object (self) as its delegate.

What’s that you say? You haven’t made the ViewController class a URL connection delegate? You’re absolutely right. You should get on that.

Becoming an NSURLConnection Delegate

You can now follow the same steps you took to make ViewController a delegate of the web view to turn it into an NSURLConnection delegate as well. There’s no practical limit on how many objects your object can be a delegate for.

The first step is to adopt the protocols that make your class a delegate. NSURLController declares a couple of different delegate protocols, and you’re free to adopt the ones that make sense to your app. In this case, you want to adopt the NSURLConnectionDelegate andNSURLConnectionDataDelegate protocols. Do this by adding those protocol names to your ViewController class, in your ViewController.swift file, like this:

class ViewController: UIViewController, UIWebViewDelegate,
NSURLConnectionDelegate,
NSURLConnectionDataDelegate {

The NSURLConnectionDelegate defines functions that get sent to your delegate when key events occur. There are a slew of messages that deal with how your app responds to authenticated content (files on the web server that are protected by an account name and password). None of that applies to this app. The only function you’re interested in is connection(_:,didFailWithError:). That message is sent if the request fails for some reason. Open your ViewController.swift file and add this new function:

func connection( connection: NSURLConnection!, image
didFailWithError error: NSError! ) {
shortLabel.title = "failed"
clipboardButton.enabled = false
shortenButton.enabled = true
}

It’s unlikely that a URL shortening request would fail. The only likely cause would be that your iPhone has temporarily lost its Internet connection. Nevertheless, you want your app to behave itself and do something intelligent under all circumstances. This function handles a failure by doing three things.

· Sets the short URL label to “failed,” indicating that something went wrong

· Disables the “copy to clipboard” button because there’s nothing to copy

· Turns the “shorten URL” button back on so the user can try again

With the unlikely stuff taken care of, let’s get to what should happen when you send a request. The NSURLConnectionDataDelegate protocol functions are primarily concerned with how your app gets the data returned from the server. It, too, defines a bunch of other functions you’re not interested in. The two you are interested in are connection(_:,didReceiveData:) and connectionDidFinishLoading(_:). Start by adding this connection(_:,didReceiveData:) function to your class:

func connection( connection: NSURLConnection!, didReceiveData data: NSData! ) {
shortURLData?.appendData(data)
}

The X.co service returns the shortened URL in the body of the HTTP response as a simple string of ASCII characters. Your delegate object will get a connection(didReceiveData:) call every time new body data has been received from the server. In this app, that’s probably going to be only once since the amount of data you’re requesting is so small. If your app requested a lot of data (such as an entire web page), this function would get called multiple times.

The only thing this function does is take the data that was received (in the data parameter) and add it to the buffer of data you’re maintaining in shortURLData. Remember the shortURLData = NSMutableData() statement in shortenURL()? That statement set up an empty buffer (NSMutableData) before the request was started. As you receive the answer to that request, it accumulates in your shortURLData variable. Does that all make sense? Let’s move on to the final function.

The last function should be self-explanatory by now. The connectionDidFinishLoading(_:) function is called when the transaction is complete: you’ve sent the URL request, you’ve received all of the data, and the whole thing was a success. Add this function to your implementation:

func connectionDidFinishLoading( connection: NSURLConnection! ) {
if let data = shortURLData {
let shortURLString = NSString(data:data, encoding:NSUTF8StringEncoding)
shortLabel.title = shortURLString
clipboardButton.enabled = true
}
}

The first two statements turn the ASCII bytes you received in connection(_:,didReceiveData:) into a string object. String objects use Unicode character values, so turning a string of bytes into a string of characters requires a little conversion.

Tip If you need to convert string objects to or from other forms, such as character or byte arrays, it would help to learn a little about Unicode characters. There’s a great article for beginners, titled “The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!),” by Joel Spolsky at http://joelonsoftware.com/articles/Unicode.html.

The third line sets the title of the shortLabel toolbar button to the short URL you just received (and converted). This makes the short URL appear at the bottom of the screen.

The last step is to turn on the “copy to clipboard” button. Now that your app has a valid short URL, it has something to copy.

Testing the Service

You’re almost ready to test your app; there’s just one tiny detail to attend to first. You’ve written the code that sends the request to the X.co service, you’ve set up delegate functions to collect the data that comes back, and you’ve written code to deal with any problems. The only thing left to do is connect the “shorten URL” button in the interface to your shortenURL(_:) function, so all that happens when you tap the button.

Select the Main.storyboard file. Holding down the Control key, click the “shorten URL” button and connect its action to the view controller. Release the mouse button and choose the shortenURL: action, as shown in Figure 3-25.

image

Figure 3-25. Connecting the “shorten URL” button

Run your app and enter a URL. In the example shown in Figure 3-26, I’ve entered http://www.apple.com. When the page loads, the “shorten URL” button becomes active. Tap it and, within a second or two, a short URL to this page appears in the toolbar (on the right in Figure 3-26).

image

Figure 3-26. The Shorty app in action

This calls for a celebration! You’ve created a remarkably sophisticated app by leveraging the power of existing iOS classes, judiciously connecting the right objects, and writing action and delegate functions to handle the details.

Final Touches

You’re still not quite done. You’ve yet to write the action that copies the short URL to the system clipboard. Fortunately, that’s not difficult to code either. In your ViewController.swift file, add this function:

@IBAction func clipboardURL( AnyObject ) {
let shortURLString = shortLabel.title
let shortURL = NSURL(string: shortURLString)
UIPasteboard.generalPasteboard().URL = shortURL
}

The first line gets the text of the URL from the shortLabel button that was set by connectionDidFinishLoading(_:). The second line turns the text of the short URL into a URL object, just as you did in the loadLocation(_:) function you wrote at the beginning of the chapter. Finally, the UIPasteboard.generalPasteboard() function returns the systemwide pasteboard for “general” data—what most people think of as the clipboard. You set the URL property of that pasteboard object to the URL object you just created. And almost as if by magic, the short URL is now on the clipboard.

Now you can use Interface Builder to connect the “copy to clipboard” button to the clipboardURL(_:) function. Do this the same way you connected the “shorten URL” button (refer to Figure 3-25).

With everything hooked up, run your app again. You should get in the habit of running your app as you write it, testing each new feature and function as it is developed. In the simulator, go to a URL and generate a shortened one, as shown on the left in Figure 3-27. Once you have a shortened URL, tap the Copy button.

image

Figure 3-27. Testing the clipboard

Tap in the text field again and clear the field. Hold down your mouse (simulated finger) until the Paste pop-up button appears (second image in Figure 3-27). Tap the paste button, and the shortened URL will be pasted into the field. This would also work with any other app that allows you to paste text.

As a final test, tap the Go button. The shortened URL will be sent to the X.co server, the server will redirect your browser to the original URL, and the web page you started at will reappear in the browser, along with the original URL in the text field.

Cleaning Up the Interface

Your app is fully functional, but there are still a few quirks in the interface. With the simulator still running, choose the Hardware image Rotate Left command. This simulates turning the device 90° counterclockwise, as shown in Figure 3-28. Most of it still looks OK, but the buttons in the bottom toolbar get squished over to the left, which looks cheesy.

image

Figure 3-28. Testing device rotation

Quit the simulator, change the project destination in the toolbar to iPad simulator, and run your app again. Not too bad, but again all of the toolbar buttons are piled up on the left.

Quit the simulator or click the stop button in Xcode. Select the Main.storyboard file. In the library, find Flexible Space Bar Button Item. This object, with a ridiculously long name, acts as a “spring” that fills the available space in a toolbar so the button objects on either side get pushed to the edge of the screen.

Drag one flexible space item object and drop it between the “shorten URL” button and the short URL field. Drop a second between the URL field and the “copy the clipboard” button, as shown in Figure 3-29.

image

Figure 3-29. Adding flexible space bar button items

With two flexible items, the “springs” share the empty space, causing the label in the middle to be centered and the copy button to shift all the way to the right. It’s not obvious in portrait orientation, but if you rotate the device to landscape, it works perfectly. Switch back to the iPhone simulator, run your app (see Figure 3-30), and rotate the device to the left (or right). Now the toolbar looks much nicer (on the right in Figure 3-30).

image

Figure 3-30. Testing iPhone rotation

Summary

This was a really important chapter, and you made it through with flying colors. You learned a lot about the fundamentals of iOS app development and Xcode’s workflow. You will use these skills in practically every app you develop.

You learned how to whip up a web browser, something that can be used in a lot of ways, not just displaying web pages. For example, you can create static web content by adding .html resource files to your app and have a web view load those files. The web view class will also let you interact with its content using JavaScript, opening all kinds of possibilities.

Learning to create, and connect, outlets is a crucial iOS skill. As you’ve discovered, an iOS app is a web of objects, and outlets are the threads that connect that web.

Most importantly, you learned how to write action functions and create delegates. These two patterns appear repeatedly throughout iOS.

In the next chapter, I’ll explain how events turn a finger touch into an action.