Show Menu

Need an iOS Developer?

Submit your 30 day Job Listing for FREE

In this watchOS 2 Tutorial I am going to show you how to download files to Apple Watch using NSURLSession. Not to be confused with ‘watchos sendmessage’.

This WatchKit tutorial assumes that you have at least a basic understanding of the Swift Programming Language.

Create watchOS Application

The first thing you need to do is create a Single View Application in xCode. This will be the basic application that we will attach our watchOS 2 app to.

Add watchOS 2 target

In Xcode, go to File -> New -> Target. This will open up a dialogue window for you. From this new window you need to click Watch OS form the left hand panel and then click WatchKit App from the options that will appear:

watchkit target watchos2

Click Next. You will then see another screen where you will name your app. Here I have just called mine ‘Watch’.

Make sure that you have selected all three checkboxes:

  • Include Notification Screen
  • Include Glance Scene
  • Include Complication
watchkit app choose options

Click Finish

Just one more, Xcode might ask you to: Activate “Watch” scheme? : This scheme has been created for the “Watch” target. Choose Activate to use this scheme for building and debugging. Schemes can be chosen in the toolbar or Product menu. Click Activate:

Activate Watch scheme

Create Watch Interface

In your project navigation, which is the left hand side panel of Xcode, you will now see new folders added to your project. Open the folder called Watch and select interface.storyboard:

watch storyboard sidebar

This will now load up the view controller that we will add our elements to:

watchOS 2 interface storyboard

Click on the above ViewController. Now, in the bottom right of Xcode, you will see the Object Library. Search for a Label and drag on onto the WatchOS View. Do the same for a button. Now your WatchOS Interface Controller should look like:

watchkit interface elements

Next, Double click on the label you just dragged onto the view so that you can edit the text and change it to “waiting…”. Do the same for the button but change the text to “Start Download”:

watchos elements labelled

The next thing you need to do is select the label, in the Attributes Inspector (Top Right of Xcode) change the settings for Lines to 4:

watchOS Attributes inspector

In the top right of Xcode, Click the Show the Assistant Editor button:

show assistant editor

This should now show a split screen of your watchOS 2 Storyboard and the InterfaceController.Swift file.

Link up your elements

From your storyboard, Click your Label whilst holding down the Control button and drag that into your InterfaceController.Swift. This will then upon up a new dialogue for you:

watchOS 2 tutorial iboutlet

For the name, enter : watchStatusLabel and click Connect. This will create an IBOutlet in your code:


@IBOutlet var watchStatusLabel: WKInterfaceLabel!

Do the same for your button but this time use downloadAction for the name and choose Action for the connection:

watchOS 2 IBAction

This will create an IBAction function:


@IBAction func downloadAction() {
}

ViewController delegation

Now we need to make your InterfaceController a delegate of: NSURLSessionDelegate and NSURLSessionDownloadDelegate. In the file, change the line:


class InterfaceController: WKInterfaceController {

to this:


class InterfaceController: WKInterfaceController, NSURLSessionDelegate, NSURLSessionDownloadDelegate {

Download Files to Watch App

Finally, let me show you how to, in watchOS Download Files and let’s get to some nitty gritty coding. I am going to show you how to download files to your watchOS application using the method URLSession(_:downloadTask:didFinishDownloadingToURL:) from NSURLSessionDownloadDelegate. From the apple docs:

The NSURLSessionDownloadDelegate protocol defines delegate methods that you should implement when using NSURLSession download tasks. In addition to these methods, be sure to implement the methods in the NSURLSessionTaskDelegate and NSURLSessionDelegate protocols to handle events common to all task types and session-level events, respectively.

Now, URLSession(_:downloadTask:didFinishDownloadingToURL:) will be called when the file we are trying to get is downloaded to a place where it is accessible from our WatchKit App. From this method we are granted two permissions; one is to allow us to read the file directly from the url supplied and it also allows us to move the file using NSFileManager to read at a later time.

The File that is downloaded is temporary and will be deleted by watchOS when the method returns. If you read the file directly from the supplied URL it is a good idea to do that in a sperate thread so that we do not interrupt or block the private queue from NSURLSession

In your view controller implement the method below, we need to create an identifier so that we can control the task after we have started it:


func URLSession(session: NSURLSession,
    downloadTask: NSURLSessionDownloadTask,
    didFinishDownloadingToURL location: NSURL) {
      
      let fm = NSFileManager()
      let url = try! fm.URLForDirectory(.DownloadsDirectory,
        inDomain: .UserDomainMask,
        appropriateForURL: location, create: true)
        .URLByAppendingPathComponent("file.txt")
      
      do{
        try fm.removeItemAtURL(url)
        try fm.moveItemAtURL(location, toURL: url)
        self.status = "Download finished"
      } catch let err{
        self.status = "Error = \(err)"
      }
}

In the above method, also add:


session.invalidateAndCancel()

Which will allow us to reuse the identifier at a later time. you do not have to do this, but I am telling you to – If you do not include session.invalidateAndCancel(), the next time that the user taps on the button in your watchOS Application, nothing will happen. Nada!

There are a few more methods that we need to implement from the class:


func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
   status = "Downloaded \(bytesWritten) bytes..."
}

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
   status = "Resuming the download"
}

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
   if let err = error{
      status = "Completed with an error = \(err)"
   } else {
      status = "Finished"
   }
}

func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) {
   if let err = error{
      status = "Invalidated \(err)"
   } else {
      //There were no errors, Fantastic
   }
}

Here is a summary of all the methods that we have used:

Method Action
task:didCompleteWithError: Tells the delegate that the task finished transferring data.
task:didReceiveChallenge:completionHandler: Requests credentials from the delegate in response to an authentication request from the remote server.
task:didSendBodyData:totalBytesSent: totalBytesExpectedToSend: Periodically informs the delegate of the progress of sending body content to the server.
task:needNewBodyStream: Tells the delegate when a task requires a new request body stream to send to the remote server.
task:willPerformHTTPRedirection:newRequest: completionHandler: Tells the delegate that the remote server requested an HTTP redirect.

An NSURLSession object need not have a delegate. If no delegate is assigned, a system-provided delegate is used, and you must provide a completion callback to obtain the data.

Remember way back to the beginning when we created out IBAction for the button? Now we are going to do something with it.

When the user taps the button, we are going to download this file http://d3f1unw9qjoxpk.cloudfront.net/tutorials/swift/watchos-2-downloading-files/file.txt , yup. we created the file for you. Cos’ we are awesome.

First, we need to define the URL with the link above. In the IBAction we created earlier:


@IBAction func downloadAction() {
}

Create a Swift Constant:


let fileUrl = NSURL(string: "http://d3f1unw9qjoxpk.cloudfront.net/tutorials/swift/watchos-2-downloading-files/file.txt")!

Now we are going to create a background URL configuration, which can be used with NSURLSession:


let id = "se.pixolity.app.backgroundtask"
let config = NSURLSessionConfiguration
   .backgroundSessionConfigurationWithIdentifier(id)

The next thing we need to do is create a task that will download the file:


let session = NSURLSession(configuration: config, delegate: self, delegateQueue: NSOperationQueue())
let req = NSURLRequest(URL: fileUrl)

session.downloadTaskWithRequest(req).resume()

Finally, at the beginning I am going to create a property called status because the NSURLSession gets called on private queues. This will display the messages through this code on the main thread.


var status: String = ""{
  didSet{
    dispatch_async(dispatch_get_main_queue()){[unowned self] in
       self.watchStatusLabel.setText(self.status)
    }
  }
}

Voila, You have successfully created a watchOS Watchkit App that can download files. Congrats.

Errors

# Since Xcode 7.1 beta 3 release

Oh Sigh!!! , I can hear you all 😛 But.. this is something that I need to mention. You might get this error:

Error Domain=NSCocoaErrorDomain Code=4 ““file.txt” couldn’t be removed.”

or even this one:

Error Domain=NSPOSIXErrorDomain Code=2 “No such file or directory”

This is because:

This is a known bug according to the Xcode 7.1 beta 3 release notes: “When running in the iOS simulator, an app cannot communicate with TCP/IP services locally hosted by the Mac. (22453539)” SO

So, to test this fully you will need to test it on an Apple Watch Device.

Download the Source Files

Summary

In this watchOS 2 Tutorial I have shown you how to create a WatchKit Extension, how to link up UIElements and how to get a file form a server using NSURL and NSURLSession and shown you how to process that file in watchOS using the delegate methods; URLSession:task:didCompleteWithError:, URLSession:task:didReceiveChallenge:completionHandler:, URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:, URLSession:task:needNewBodyStream:, URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:. You have also seen how to bring something to the main thread from a private queue using: dispatch_async(dispatch_get_main_queue()).

having issues?

We have a Questions and Answer section where you can ask your iOS Development questions to thousands of iOS Developers.

Ask Question

FREE Download!

Get your FREE Swift 2 Cheat Sheet and quick reference guide PDF download when you sign up to SwiftMonthly


Sharing is caring

If you enjoyed this tutorial, please help us and others by sharing using one of the social media buttons below.


iOS-Blog Admin Team

Written by:

We're here to help.

Comments

comments