Get immediate access to this and over 1,500+ other videos and books.

Boost your skills with a raywenderlich.com Beginner subscription. With over 60+ video courses and our core foundational programming books bundled in one subscription, it’s simply the best investment you can make in your development career.

Pause, Resume & Cancel Downloads

Apps will likely run into scenarios that require a download or upload to be paused, resumed, or canceled entirely. This episode will cover these topics.

Contributors

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

You can unlock the rest of this video course, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

When users start to download a file, they may later change their mind about the download. The file may be too large or their connection is too slow. Or maybe, they need to pause the download so they can download another file first.

import SwiftUI
class MutableSongDownloader: NSObject, ObservableObject {
  @Published var downloadLocation: URL?
}
private lazy var session: URLSession = {
  let configuration = URLSessionConfiguration.default
      
  return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
extension MutableSongDownloader: URLSessionDownloadDelegate {
  func urlSession(_ session: URLSession,
                  downloadTask: URLSessionDownloadTask,
                  didWriteData bytesWritten: Int64, 
                  totalBytesWritten: Int64,
                  totalBytesExpectedToWrite: Int64) {

  }
  
  func urlSession(_ session: URLSession,
                  downloadTask: URLSessionDownloadTask,
                  didFinishDownloadingTo location: URL) {
    
  }
  
  func urlSession(_ session: URLSession,
                  task: URLSessionTask,
                  didCompleteWithError error: Error?) {
    
  }
}
private var downloadURL: URL?
private var downloadTask: URLSessionDownloadTask?
func downloadSong(at url: URL) {
  downloadURL = url
    
  downloadTask = session.downloadTask(with: url)
  downloadTask?.resume()
}
@Published var downloadProgress: Float = 0
func urlSession(_ session: URLSession,
                downloadTask: URLSessionDownloadTask,
                didWriteData bytesWritten: Int64, totalBytesWritten: Int64,
                totalBytesExpectedToWrite: Int64) {
  Task {
    await MainActor.run {
      downloadProgress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
    }
  }
}       
func urlSession(_ session: URLSession,
                task: URLSessionTask,
                didCompleteWithError error: Error?
  ) {
  Task {
    await MainActor.run {
      if let httpResponse = task.response as? HTTPURLResponse,
         httpResponse.statusCode != 200 {
          
        print("Request failed.")
      }
    }
  }
}
func urlSession(_ session: URLSession,
                downloadTask: URLSessionDownloadTask,
                didFinishDownloadingTo location: URL
  ) {
  let fileManager = FileManager.default
    
  guard let documentsPath = fileManager.urls(for: .documentDirectory,
                                             in: .userDomainMask).first,
        let lastPathComponent = downloadURL?.lastPathComponent
  else {
  print("Document directory error.")
      
  return
  }
    
  let destinationURL = documentsPath.appendingPathComponent(lastPathComponent)
    
  do {
    if fileManager.fileExists(atPath: destinationURL.path) {
      try fileManager.removeItem(at: destinationURL)
    }
        
    try fileManager.copyItem(at: location, to: destinationURL)
      
    Task {
      await MainActor.run {
        downloadLocation = destinationURL
      }
    }
  } catch {
    Task {
      await MainActor.run {
        print("Error copying song.")
      }
    }
  }
}

Time to Support Cancel, Pause, And Resume

Because MutableSongDownloader can pause, resume, or cancel, you want a way to keep track of the state of your download. To do so, add the following enum inside of your class:

enum State {
  case paused
  case downloading
  case failed
  case finished
  case waiting
}
var state: State = .waiting
state = .downloading
func cancel() {
  state = .waiting
   
 downloadTask?.cancel()
    
  Task {
    await MainActor.run {
      downloadProgress = 0
    }
  }
}
private var resumeData: Data?
func pause() {
  downloadTask?.cancel(byProducingResumeData: { data in
    Task {
      await MainActor.run {
        self.resumeData = data
          
        self.state = .paused
      }
    }
  })
}
func resume() {
 guard let resumeData = resumeData else {
   return
 }
   
 downloadTask = session.downloadTask(withResumeData: resumeData)
 downloadTask?.resume()
   
 state = .downloading
}
Task {
  await MainActor.run {
    state = .failed
  }
} 
state = .finished
state = .failed
state = .failed
@ObservedObject private var mutableDownloader: MutableSongDownloader = MutableSongDownloader()
private func mutableDownloadTapped() {
  switch mutableDownloader.state {
  case .downloading:
    mutableDownloader.pause()
      
  case .failed, .waiting:
    guard let previewURL = musicItem.previewURL else {
      return
    }
      
    mutableDownloader.downloadSong(at: previewURL)

  case .finished:
    playMusic = true
      
  case .paused:
    mutableDownloader.resume()
  }
}
AudioPlayer(songUrl: mutableDownloader.downloadLocation!)
if mutableDownloader.state == .paused || mutableDownloader.state == .downloading {
  ProgressView(value: mutableDownloader.downloadProgress)
}
Button<Text>(action: mutableDownloadTapped) {
  switch mutableDownloader.state {
  case .downloading:
    return Text("Pause")
                
  case .failed:
    return Text("Retry")
                
  case .finished:
    return Text("Listen")
                
  case .paused:
    return Text("Resume")
                
  case .waiting:
    return Text("Download")
  }
}

Reviews

Comments