Updating UI Dispatch_Async background download Swift - ios

Im working on a folders/files application where users are able to download files to local disk. Whenever a user is downloading a file, I want to show a download bar that displays progress.
to do so, I've created a protocol that allows my download class and my view controller to communicate:
protocol:
protocol DownloadResponder : class {
func downloadFinished()
func downloadProgress(current:Int64, total:Int64)
}
download class:
class fileDownloader: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate {
//responder
var responder : MyAwesomeDownloadResponder?
init(responder : MyAwesomeDownloadResponder) {
self.responder = responder
}
...
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
println("downloaded \(100*totalBytesWritten/totalBytesExpectedToWrite)")
responder?.downloadProgress(totalBytesWritten, total: totalBytesExpectedToWrite)
}
...
}
and then in my view controller I have my download button which trigger the downloadProgress function:
func downloadProgress(current:Int64, total:Int64) {
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
var currentProgress = 100 * current / total
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.downloadLbl.text = "Downloaded \(currentProgress)%"
//set progress bar
self.progressBar.setProgress(Float(currentProgress), animated: true)
}
}
}
While printing information in the console works all the time, updating the UI was not really stable. To fix this I used the dispatch_async method that push the UI change on the main thread. However, while it always work on the first time, poping back to the previous view controller and coming back again, executing the download once more does not trigger the UI updates. The progress bar progressBar.setProgress does nothing and my label downloadLbl.text does not update itself.
Does anyone have an idea about the way to solve this?
If my question lacks information, please let me know and I'll try to add up to the existing information. Thanks!

As I didn't receive / find any solution to my problem I went back to an higher level and changed the way to communicate between my classes to handle ui changes based on background download thread progression.
Instead of using protocols, I went for Notifications and it solved my problem.
Inside the download class:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
println("downloaded \(100*totalBytesWritten/totalBytesExpectedToWrite)")
//NOTIFICATION
// notify download progress!
var fileInfo = [NSObject:AnyObject]()
fileInfo["fileId"] = fileDownloader.storageInfo[downloadTask.taskIdentifier]!["id"] as! Int!
fileInfo["fileCurrent"] = Float(totalBytesWritten)
fileInfo["fileTotal"] = Float(totalBytesExpectedToWrite)
let defaultCenter = NSNotificationCenter.defaultCenter()
defaultCenter.postNotificationName("DownloadProgressNotification",
object: nil,
userInfo: fileInfo)
}
inside the view controller:
override func viewDidLoad() {
super.viewDidLoad()
// ready for receiving notification
let defaultCenter = NSNotificationCenter.defaultCenter()
defaultCenter.addObserver(self,
selector: "handleCompleteDownload:",
name: "DownloadProgressNotification",
object: nil)
}
func handleCompleteDownload(notification: NSNotification) {
let tmp : [NSObject : AnyObject] = notification.userInfo!
// if notification received, change label value
var id = tmp["fileId"] as! Int!
var current = tmp["fileCurrent"] as! Float!
var total = tmp["fileTotal"] as! Float!
var floatCounter = 100 * current / total
var progressCounter = String(format: "%.f", floatCounter)
if(id == self.fileId){
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.downloadLbl.text = "Downloaded \(progressCounter)%"
self.progressBar.setProgress((progressCounter as NSString).floatValue, animated: true)
}
}
}
}
hope that will help!

Related

Apple Sample Code for WatchKit Extension Background Refresh

I've been searching for the answer to this question for quite a long time, so I think I'm willing to risk some downvotes to post it.
Basically, I want to make the Apple provided sample code for Apple Watch background refresh actually work (link and code below).
I've tried both in the simulator and on an iPhone 6s with an Apple Watch Series 2, and the background tasks are never successfully completed to the point where the time updates. I've tried pinning the watch app to the dock, and I've tried keeping the app in the foreground and sending it to the background, both in the simulator and on the actual watch. I even tried waiting almost a year to see if Xcode or the Apple Watch would receive an update that would make it work.
Has anyone successfully modified the Apple provided code to make it work?
You can download the entire runnable sample project here: WatchBackgroundRefresh: Using WKRefreshBackgroundTask to update WatchKit apps in the background
/*
Copyright (C) 2016-2017 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
Abstract:
The main interface controller.
*/
import WatchKit
import Foundation
class MainInterfaceController: WKInterfaceController, WKExtensionDelegate, URLSessionDownloadDelegate {
// MARK: Properties
let sampleDownloadURL = URL(string: "http://devstreaming.apple.com/videos/wwdc/2015/802mpzd3nzovlygpbg/802/802_designing_for_apple_watch.pdf?dl=1")!
#IBOutlet var timeDisplayLabel: WKInterfaceLabel!
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .long
return formatter
}()
// MARK: WKInterfaceController
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Configure interface objects here.
WKExtension.shared().delegate = self
updateDateLabel()
}
// MARK: WKExtensionDelegate
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task : WKRefreshBackgroundTask in backgroundTasks {
print("received background task: ", task)
// only handle these while running in the background
if (WKExtension.shared().applicationState == .background) {
if task is WKApplicationRefreshBackgroundTask {
// this task is completed below, our app will then suspend while the download session runs
print("application task received, start URL session")
scheduleURLSession()
}
}
else if let urlTask = task as? WKURLSessionRefreshBackgroundTask {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: urlTask.sessionIdentifier)
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
print("Rejoining session ", backgroundSession)
}
// make sure to complete all tasks, even ones you don't handle
task.setTaskCompleted()
}
}
// MARK: Snapshot and UI updating
func scheduleSnapshot() {
// fire now, we're ready
let fireDate = Date()
WKExtension.shared().scheduleSnapshotRefresh(withPreferredDate: fireDate, userInfo: nil) { error in
if (error == nil) {
print("successfully scheduled snapshot. All background work completed.")
}
}
}
func updateDateLabel() {
let currentDate = Date()
timeDisplayLabel.setText(dateFormatter.string(from: currentDate))
}
// MARK: URLSession handling
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("NSURLSession finished to url: ", location)
updateDateLabel()
scheduleSnapshot()
}
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: NSUUID().uuidString)
backgroundConfigObject.sessionSendsLaunchEvents = true
let backgroundSession = URLSession(configuration: backgroundConfigObject)
let downloadTask = backgroundSession.downloadTask(with: sampleDownloadURL)
downloadTask.resume()
}
// MARK: IB actions
#IBAction func ScheduleRefreshButtonTapped() {
// fire in 20 seconds
let fireDate = Date(timeIntervalSinceNow: 20.0)
// optional, any SecureCoding compliant data can be passed here
let userInfo = ["reason" : "background update"] as NSDictionary
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fireDate, userInfo: userInfo) { (error) in
if (error == nil) {
print("successfully scheduled background task, use the crown to send the app to the background and wait for handle:BackgroundTasks to fire.")
}
}
}
}
The following is output when run on the simulator. Similar output (but not necessarily exactly the same) when running in other configurations:
successfully scheduled background task, use the crown to send the app to the background and wait for handle:BackgroundTasks to fire.
received background task: <WKSnapshotRefreshBackgroundTask: 0x7b019030>
received background task: <WKApplicationRefreshBackgroundTask: 0x7a711290>
application task received, start URL session
For anyone who may find this, there were 2 problems that I saw, both with the URLSession scheduling. With these changes, I think the Apple sample code actually works, at least on the simulator.
-The sampleDownloadURL needs to be secure, so a URL with HTTPS is necessary. This one works: https://api.weather.gov/points/42.3584,-71.0598/forecast
-It looks to me like the delegate for the URLSession was never set to self, so the following change fixed that:
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
The following working (although less complete) code was very helpful: What's New in watchOS 3: Background Tasks
Edit: One more issue that may occur is that the tasks are completed immediately after they are received. In practice, they should be saved (in a local variable, for instance) and then completed after all processing for that task is complete. For this code, I think that means hanging onto the WKApplicationRefreshBackgroundTask and not calling setTaskCompleted() on it until right after the call to scheduleSnapshot.

Swift 3: Handle Errors in URLSession Delegates

I need to know how to catch errors (interruptions, primarily) in a URLsession with delegates.
I have the following Swift function within a custom class, which downloads a small file to test download speeds:
func testSpeed() {
Globals.shared.dlStartTime = Date()
Globals.shared.DownComplete = false
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
let task = session.downloadTask(with: url!)
if Globals.shared.currentSSID == "" {
Globals.shared.bandwidth = 0
Globals.shared.DownComplete = true
session.invalidateAndCancel()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ProcessFinished"), object: nil, userInfo: nil)
} else {
print("Running Task")
task.resume()
}
}
This class uses URLSessionDelegate and URLSessionDownloadDelegate. Here are the current delegates it calls:
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
Globals.shared.dlFileSize = (Double(totalBytesExpectedToWrite) * 8) / 1000
let progress = (Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)) * 100.0
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ProcessUpdating"), object: nil, userInfo: ["progress" : progress])
}
^ That one monitors download progress and uses NotificationCenter to send the progress back to the view controller.
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Done")
if Globals.shared.DownComplete == false {
let elapsed = Double( Date().timeIntervalSince(Globals.shared.dlStartTime))
Globals.shared.bandwidth = Int(Globals.shared.dlFileSize / elapsed)
Globals.shared.DownComplete = true
Globals.shared.dataUse! += (Globals.shared.dlFileSize! / 8000)
}
session.invalidateAndCancel()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ProcessFinished"), object: nil, userInfo: nil)
}
^ That one just calculates the speed once the download is finished, and sends the result to a global variable in another class. Unimportant.
As of now, when I test my application, interrupting the download just hangs the app, because it keeps waiting for the processFinished NC call, which obviously never comes.
Is there another delegate I should add to catch that interruption, or am I missing something more obvious?
You can use urlSession:task:didCompleteWithError delegate method

show progress indicator for multiple image upload in UITableViewCell

I have UITableView with custom cells. I want to show progress indicator for multiple images upload.
I have tried reloadRowAtIndexPath method of UITableView but it not sufficient solution because cell is continuously blinks which looks weird.
Another one solution i found is to store reference of my progress indicator view placed in UITableViewCell in global variable and then modify it outside UITableView datasource methods, but in this solution i faced one problem which is i have to keep track of multiple progress indicator view objects of UITableViewCell which is difficult because UITableView datasource is two dimensional NSMutableArray(In short array inside array) so i don't have unique IndexPath.row because of multiple sections. So how can i manage objects of progress indicator view?
And also Is there any better solution to do this type of job?
Ok, so here is what I used in one of my projects when I could not find anything else.
Swift 3
Make a sub class of NSObject (because a sub class of URLSession won't let you set configuration and other parameters as the only designated initializer there is init()) that includes the information of the cell that started the upload process as in IndexPath and also a URLSession object.
Use this sub class to create new URLSession whenever you want to upload (I used uploadTask method of URLSession).
Create uploadTask and start uploading.
You will also have to make your own protocol methods that are called by normal protocol methods of URLSession, to send your custom sub class instead of URLSession object to the delegate you want.
Then in that delegate, you may check for the information of indexPath that is stored in the custom sub class you got from the previous step and update the appropriate cell.
The same could be achieved by using Notifications I guess.
Below is the screenshot of the sample application I wrote:
public class TestURLSession:NSObject, URLSessionTaskDelegate {
var cellIndexPath:IndexPath!
var urlSession:URLSession!
var urlSessionUploadTask:URLSessionUploadTask!
var testUrlSessionDelegate:TestURLSessionTaskDelegate!
init(configuration: URLSessionConfiguration, delegate: TestURLSessionTaskDelegate?, delegateQueue queue: OperationQueue?, indexPath:IndexPath){
super.init()
self.urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: queue)
self.cellIndexPath = indexPath
self.testUrlSessionDelegate = delegate
}
func uploadTask(with request: URLRequest, from bodyData: Data) -> URLSessionUploadTask{
let uploadTask = self.urlSession.uploadTask(with: request, from: bodyData)
self.urlSessionUploadTask = uploadTask
return uploadTask
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64){
self.testUrlSessionDelegate.urlSession(self, task: self.urlSessionUploadTask, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
}
}
protocol TestURLSessionTaskDelegate : URLSessionDelegate {
func urlSession(_ session: TestURLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
}
Edits are welcome.
Here the solution which i applied, may be helpful to someone who wants same implementations as i want, without using third party library or classes.
I have created one custom UIView and design circular progress indicator using CALayer and some animations. This is not a big deal. But the thing which is difficult for me is i want this progress indicator in several cells which indicates multiple image progress in percentages.
So i have created one custom class with properties like
#property (nonatomic,retain) NSIndexPath *indexPath;
#property (nonatomic,strong) NSURLSessionTask *uploadtask;
Then i maintain one NSMutableArray which contains my custom class objects which has values for each uploadTask for currently uploading images and merged string which contains indexPath. Now i have track of my all uploading images so i have change uploaded percentage in my custom progress indicator UIView with help of indexPath values whenever i receive response from delegate method of NSURLSession.
I had a similar stuff to do where in which I wanted to download files and show progress bar. My idea was to create a Custom object which keep track of a particular download and all the cells will internally listen to the changes in this object. Each cell will have its own object uniquely identified by the task identifier. I have written a sample code in Swift 3 available in the below link (skeleton code also added)
FileDownloader
class DownLoadData: NSObject {
var fileTitle: String = ""
var downloadSource: String = ""
var downloadTask: URLSessionDownloadTask?
var taskResumeData: Data?
var downloadProgress: Float = 0.0
var isDownloading: Bool = false
var isDownloadComplete: Bool = false
var taskIdentifier: Int = 0
var groupDownloadON:Bool = false
var groupStopDownloadON:Bool = false
init(with title:String, and source:String){
self.fileTitle = title
self.downloadSource = source
super.init()
}
func startDownload(completion:#escaping (Result<Bool, Error>)->Void,progressHandler:#escaping (Float)->Void ){
}
func resumeDownload(completion:#escaping (Result<Bool, Error>)->Void,progressHandler:#escaping (Float)->Void ){
}
func pauseDownload(){
}
func stopDownload(){
if self.isDownloading{
}
}
func cleanUpHandlers(){
// remove the completion handlers from the network manager as resume is taken as a new task.
}
func handleDownloadSuccess(){
}
func handleDownloadError(){
}
}
Use URLSessionTaskDelegate method and do necessary calculation:
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
Below is a solution, that is tested for a single file upload. But you can modify it to support multiple file uploads. Make sure to add necessary IBOutlets and IBAction as necessary. The image is added in 'Assets.xcassets'.
My UI looks like below:
Below is the code for ViewController.
import UIKit
class UploadProgressViewController: UIViewController, URLSessionTaskDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
progressView.progress = 0.0
}
#IBAction func didTapOnStartUploadButton(_ sender: Any) {
startDownload()
}
func startDownload () {
// 1. Prepare data to download
var data = Data()
if let image = UIImage(named: "swift.jpg") {
data = image.pngData()!
}
// 2. Creation of request
var request = URLRequest(url: NSURL(string: "http://127.0.0.1:8000/swift.png")! as URL)
request.httpMethod = "POST"
request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
// 3. Configuring the Session
let configuration = URLSessionConfiguration.default
let mainqueue = OperationQueue.main
// 4. Start the upload task
let session = URLSession(configuration: configuration, delegate:self, delegateQueue: mainqueue)
let dataTask = session.uploadTask(with: request, from: data)
dataTask.resume()
}
// URLSessionTaskDelegate Handling
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print("session \(session) uploaded \(uploadProgress * 100)%.")
progressView.progress = uploadProgress
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print(error.debugDescription)
}

How to handle background url session lost internet connection?

I have background downloading zip file:
if let url = NSURL(string: urlstring)
{
let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier((NSUUID().UUIDString))
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.downloadTaskWithURL(url)
session.sessionDescription = filepath
if let sessionId = session.configuration.identifier
{
print("start zip session: " + sessionId)
}
task.resume()
}
}
it works cool if you have internet connection but if you lose it during downloading app just wait and URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) will not be called never.
How it can be handle?
Something like time for response from server
The question is old, but still unanswered, so here is one workaround.
Some clarifications first
In general there is a property of URLSessionConfiguration called waitsForConnectivity which can be set to false where URLSessionTask will then directly fail on connection lost. However if true then URLSessionTaskDelegate will receive a callback on the urlSession(_:taskIsWaitingForConnectivity:) method.
However
Background tasks such as DownloadTask always wait for connectivity and ignore waitsForConnectivity property of URLSessionConfiguration. They also DON'T trigger urlSession(_:taskIsWaitingForConnectivity:) callback, so there is no official way to listen for connectivity drop-out on download task.
Workaround
If you listening for the download progress you'll notice that a call to the method is done few times in a second. Therefore we can conclude that if the progress callback is not called for more than 5 seconds, then there could be a connectivity drop issue. So the workaround is to make additional property to the URLSessionDownloadDelegate delegate and store the last update of the progress. Then have interval function to periodically check whether this property were not updated soon.
Something like:
class Downloader: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate {
var lastUpdate: Date;
var downloadTask: URLSessionDownloadTask?;
public var session : URLSession {
get {
let config = URLSessionConfiguration.background(
withIdentifier: "\(Bundle.main.bundleIdentifier!).downloader");
config.isDiscretionary = true;
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue());
}
}
override init() {
self.lastUpdate = Date();
super.init();
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// Handle download completition
// ...
}
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten writ: Int64,
totalBytesExpectedToWrite exp: Int64)
{
let progress = 100 * writ / exp;
// Do something with the progress
// ...
self.lastUpdate = Date();
}
}
var downloader = Downloader();
let url = "http://...";
var request = URLRequest(url: URL(string: url)!);
currentWorker.downloadTask = downloader.session.downloadTask(with: request);
currentWorker.downloadTask!.resume();
// Schedule timer for every 5 secs
var timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "CheckInternetDrop", userInfo: nil, repeats: true);
func CheckInternetDrop(){
let interval = Date().timeIntervalSinceDate(downloader.lastUpdate);
if (interval > 5) {
print("connection dropped");
}
}
Very simple example...
According to apple docs on pausing and resuming downloads :
Also, downloads that use a background configuration will handle resumption automatically, so manual resuming is only needed for non-background downloads.
Besides, the reason waitsForConnectivity is being ignored is stated in the downloading files in the background :
If your app is in the background, the system may suspend your app while the download is performed in another process.
This is why even if you were to build a timer in a similar fashion to Dimitar Atanasov it will not work once the app goes in the background since it will be suspended.
I've tested the background downloading myself with network failures and the system resumes without fail, hence no extra code is required.
You can set timeouts for your requests:
config.timeoutIntervalForRequest = <desired value>
config.timeoutIntervalForResource = <desired value>
Documentation:
timeoutIntervalForRequest
timeoutIntervalForResource

How to add NSURL session progress to table view cell

I have a Calculus Video app I created based on tableviews and I am trying to add the functionality for offline saving of video files. I understand what I am trying to achieve but I am getting stumped by adding the progress bar to the specific cells:
Currently, the download is started by clicking on the accessory button. I have the following method
override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
//Code to save Video to Documents directory goes here
let currentVideo = videos[indexPath.section][indexPath.row]
guard currentVideo.saved == false else {
print("Video is already saved")
return
}
guard let url = currentVideo.url else {
print("Video not found...url is invalid")
return
}
guard currentVideo.downloading == false else {
print("Video is already downloading")
return
}
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
delegate: self,
delegateQueue: NSOperationQueue.mainQueue())
let downloadTask = session.downloadTaskWithURL(url)
downloadTask.resume()
}
Now, I am implementing the NSURLSessionDownloadDelegate methods, of which the relevant one is below
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
print(progress) //this works and shows progress
}
Now, all I want to do is to update the property
currentVideo.progress = progress
//where currentVideo is the video for the cell that was tapped
The problem is I do not know how to get the current video inside of this delegate method. I was trying to somehow figure out how to use the downloadTask.taskIdentifier or something like that but I am not able to figure it out. Can somebody please point me in the right direction?
You can try it following way.
Declare global variable under your class
var selectedIndex:NSIndexPath!
Then in accessoryButtonTappedForRowWithIndexPath method
selectedIndex = indexPath
Now, in delegate method downloadTask assign value
let currentVideo = videos[selectedIndex.section][selectedIndex.row]
currentVideo = // Your value

Resources