watchOS: Complications

Feb 7 2023 · Swift 5.6, watchOS 8.5, Xcode 13

Part 1: Introduction to Complications

07. Update with Background URL Downloads

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 06. Update with Background Tasks Next episode: 08. Update with Push Notifications

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Heads up... You've reached locked video content where the transcript will be shown as obfuscated text.

Downloading data from the network follows the same general pattern as background tasks.

URLSession setup and configuration

To start, create a file named UrlDownloader.swift

final class UrlDownloader: NSObject {

}
  let identifier: String

  init(identifier: String) {
    self.identifier = identifier
  }
  private lazy var backgroundUrlSession: URLSession = {
  
  }()
    let config = URLSessionConfiguration.background(
      withIdentifier: identifier
    )
    config.isDiscretionary = false
    config.sessionSendsLaunchEvents = true
    return .init(
      configuration: config,
      delegate: self,
      delegateQueue: nil
    )
extension UrlDownloader: URLSessionDownloadDelegate {
  func urlSession(
    _ session: URLSession,
    downloadTask: URLSessionDownloadTask,
    didFinishDownloadingTo location: URL
  ) {
  }
}

Scheduling a network download

Now, similar to how we created a background task in the previous episode, we can create a URL Sessions Download Task.

private var backgroundTask: URLSessionDownloadTask?
func schedule(firstTime: Bool = false) {
  let minutes = firstTime ? 1 : 15

  let when = Calendar.current.date(
    byAdding: .minute,
    value: minutes,
    to: Date.now
  )!
  let url = URL(
    string: "https://api.weather.gov/gridpoints/TOP/31,80/forecast"
  )!
  let task = backgroundUrlSession.downloadTask(with: url)
  task.earliestBeginDate = when
  task.countOfBytesClientExpectsToSend = 100
  task.countOfBytesClientExpectsToReceive = 12_000
  task.resume()
  backgroundTask = task
}

URLSessionDownloadDelegate

When the backgroundTask finishes downloading, watchOS will call this urlSession(_:downloadTask:didFinishDownloadingTo:) method we left empty earlier.

let decoder = JSONDecoder()
guard

else {
  return
}
guard
  location.isFileURL,
else {
  return
}
  let data = try? Data(contentsOf: location),
  let decoded = try? decoder.decode(Weather.self, from: data),
  let temperature = decoded.properties.periods.first?.temperature
else {
  return
}
UserDefaults.standard.set(temperature, forKey: "temperature")
func urlSession(
  _ session: URLSession,
  task: URLSessionTask,
  didCompleteWithError error: Error?
) {

}
  backgroundTask = nil

  DispatchQueue.main.async {
    self.completionHandler?(error == nil)
    self.completionHandler = nil
  }
}

Preparing for download

So let’s add one at the top of UrlDownloader:

private var completionHandler: ((Bool) -> Void)?
public func perform(_ completionHandler: @escaping (Bool) -> Void) {
  self.completionHandler = completionHandler
  _ = backgroundUrlSession
}

Alright, let’s tie this all together in back in ExtensionDelegate.swift

private var downloads: [String: UrlDownloader] = [:]
private func downloader(for identifier: String) -> UrlDownloader { 

}
  guard let download = downloads[identifier] else {
    let downloader = UrlDownloader(identifier: identifier)
    downloads[identifier] = downloader
    return downloader
  }
  return download
case let task as WKURLSessionRefreshBackgroundTask:
  let downloader = downloader(for: task.sessionIdentifier)
  downloader.perform { updateComplications in
  
  }
    if updateComplications {
      Self.updateActiveComplications()
    }
    
    downloader.schedule()
    task.setTaskCompletedWithSnapshot(false)
  }

Updating ContentView

In ContentView.swift, add a URLDownloader.

@State private var downloader = UrlDownloader(identifier: "ContentView")
Button {
  downloader.schedule(firstTime: true)
} label: {
  Text("Download")
}