Coding the Action

Now that we’ve added a button to our view and wired it up, we can run the app again. We can tap the Play button, but it doesn’t do anything. Did we set up our connection correctly? One thing we can do as a sanity check is to log a message to make sure our code is really running. Once that’s verified, we can move on to implementing our player functionality.

Logging

Back in Chapter 2, we learned about the print function for logging messages to the Xcode console. We can use that in our action to just log a message every time the button is tapped, and thereby verify that the connections are working. Select ViewController.swift in the Project Navigator (1) to edit its source code and rewrite handlePlayPauseTapped like this:

 @IBAction​ ​func​ handlePlayPauseTapped(_ sender: ​Any​) {
  print(​"handlePlayPauseTapped"​)
 }

Run the app again, and tap the Play button. Back in Xcode, the Debug Area automatically appears at the bottom of the window once a log or error message is generated, as seen in the following figure. Every time the button is tapped, another line is written to the log and shown in the Debug Area. If the Debug Area slides in but looks empty, check the two rightmost buttons at the bottom of the Debug Area, next to the trashcan icon; the left one enables a variables view (populated only when the app is stopped on a breakpoint), and the right (which we want to be visible) is the console view where log messages appear. Another way to force the console view to appear is to press C.

images/connecting/xcode-debug-pane.png

So now we have a button that is connected to our code, enough to log a message that indicates the button tap is being handled. The next step is to actually make it play!

Connecting to a Player

We remember from Getting Serious on the Playground, that the AV Foundation framework makes it pretty easy to play audio from a URL: we just create an AVPlayer from a URL, and tell it to play. So let’s do that here. The first thing we need to do is import the AV Foundation framework, so Xcode will let us use it in this source file. Add the import statement to the top of the file:

 import​ ​AVFoundation

Now we can use an AVPlayer to play something when the user taps the Play button. We’ll want the player to be around for as long as this view is on the screen, so let’s make it a property of this class. Add this on a new line after the ViewController’s opening curly brace:

 private​ ​var​ player : ​AVPlayer​?

We’ve made the player property an optional, since otherwise we’d have to have a player ready to go in the init, and we want to let callers tell us what to play, when they’re ready. So instead we’ll allow the player to be nil at first.

How will we create a player? Well, what makes sense for callers? The job of this scene will be to just “play something.” We could just let them call us with a URL, and we’ll try to make an AVPlayer from that. So let’s do exactly that by writing a new method:

 func​ set(url: ​URL​) {
  player = ​AVPlayer​(url: url)
 }

This method takes the URL that’s passed in, tries to create an AVPlayer from it, and populates the player property.

When the rest of our app is able to parse podcast feeds and get episode URLs from them, it’ll call this method to tell this scene what to play. That said, we’d like something to play with right now. For the time being, we’ll hard-code a URL so we can get back to handling the tap on our Play button.

When Xcode created this ViewController.swift file for us, it stubbed out trivial implementations of a few methods. One of them is viewDidLoad, whose contents look like this:

 override​ ​func​ viewDidLoad() {
 super​.viewDidLoad()
 // Do any additional setup after loading the view, typically from a nib.
 }

In other words—and please ignore the business about “typically from a nib”—this means that this method will be called once our scene is loaded from the storyboard. viewDidLoad is called only once and prior to the view actually appearing on screen. As the comment suggests, it’s a great place to do initial setup. For us, it’s where we can set our podcast URL, by calling the set(url:) method that we just wrote.

Remove the comment, and rewrite viewDidLoad as follows:

1: override​ ​func​ viewDidLoad() {
2: super​.viewDidLoad()
3: if​ ​let​ url = ​URL​(
4:  string: ​"http://traffic.libsyn.com/cocoaconf/CocoaConf001.m4a"​) {
5:  set(url: url)
6:  }
7: }

Podcast URLs

images/aside-icons/info.png

For this example, we grabbed an episode URL from the CocoaConf Podcast, which has interviews with iOS developers, authors, and speakers from the CocoaConf conference tour (including the authors). If you want to check it out, visit http://cocoaconf.com/podcast/.

Of course, feel free to use an episode of your favorite podcast, if you know how to get the URL of an individual episode. If not, don’t worry; we’ll be digging deep into the contents of podcast RSS feeds in later chapters, and you’ll see more URLs than you’ll know what to do with.

On line 2, we call the superclass’s implementation of the method, as we usually do in OO languages. Then on lines 3-4, we use the URL class’s initializer that takes a string and use that to try to create a URL with the specified MP3 or AAC URL.

If the if let on lines 3-4 successfully created a URL, we’ll make it to line 5, which calls our set(url:). We remember that calling that method will create our AVPlayer, and we will now be ready to play.

Now, finally, we’re ready to do something interesting with the button tap. Go back to the handlePlayPauseButtonTapped method, and replace the print call with a more useful implementation:

 @IBAction​ ​func​ handlePlayPauseTapped(_ sender: ​Any​) {
  player?.play()
 }

This uses the optional-chaining operator, ?, to say “if player is not nil, tell it to play.”

Network Security Concerns

We should be ready to go; when the view loads, it creates an AVPlayer, and when we get a button tap, we call play. But try it by tapping the Run button. Spoiler alert: nothing happens when you tap.

Down in the console, you might notice that when the app comes up, there’s a big error message:

 App Transport Security has blocked a cleartext HTTP (http://) resource
 load since it is insecure. Temporary exceptions can be configured via
 your app's Info.plist file

Apps built for iOS 9 or later are controlled by App Transport Security (ATS), a feature introduced in iOS 9 to compel developers to adhere to safe, secure, and private networking practices. If you’ve heard the phrase “https everywhere,” you get the gist: use secure connections wherever possible. Under App Transport Security, any attempt to load a plain http-style URL—like our podcast URL—fails immediately.

If we knew every podcast were reachable with an https:-style URL, or we were only playing media we hosted ourselves, we’d be fine. But we can’t count on that.

App Transport Security allows us to carve out exceptions to its policies, and since we’re still early in our study, we’ll use the simplest means possible. ATS has a setting that basically means “allow everything,” so that’s what we’ll use.

ATS exceptions are implemented on an app-wide basis, so they go in our apps’ settings. We can see the custom properties for our app by clicking the Pragmatic Podcasts project icon at the top of the File Navigator, choosing the PragmaticPodcasts target, and selecting the Info tab. This view has settings for things like our app version and other metadata:

images/connecting/xcode-target-custom-properties.png

This is where the App Transport Security settings go, but this UI is hard to edit visually, and even harder to explain. (Trust us on this!) So instead, we’ll go to the file where all these settings actually live. Under the Pragmatic Podcasts folder icon, select the Info.plist file. That shows the same metadata in the same hard-to-use interface. Right-click or Control-click Info.plist to expose a pop-up menu, and choose Open As Source Code. This lets us edit the raw XML.

Now, we can carve out our App Transport Security exception. Right before the </dict> at the bottom of the file, add the following:

 <key>NSAppTransportSecurity</key>
 <dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
 </dict>

What this does is basically turn off App Transport Security for the whole app. For now, this workaround gets us out of security jail.

Run the app again and tap the Play button. You should now be hearing the podcast start playing. Success!

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

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