Search code examples
iosswiftuiimagensurlrequestnsurlsession

How to run multiple NSURLSessionDownloadTask's so I can download multiple user photo streams from Instagram?


//
//  ViewController.swift
//  Fashun
//
//  Created by Alex Macleod on 20/10/14.
//  Copyright (c) 2014 Alex Macleod. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

var collectionView: UICollectionView?

var instanceOfCustomObject: CustomObject = CustomObject()
var accessToken: NSString!
var userDefaults: NSUserDefaults!
let colorWheel = ColorWheel()
var photoCount: Int! = 0
let photos = NSMutableArray()

override func viewDidLoad() {
    super.viewDidLoad()

    userDefaults = NSUserDefaults.standardUserDefaults()
    self.accessToken = userDefaults!.objectForKey("accessToken") as NSString
    println(self.accessToken)

//        instanceOfCustomObject.someProperty = "Hello World"
//        var accessToken : NSString? = NSString(instanceOfCustomObject.accessToken)
//        println(accessToken)
//        instanceOfCustomObject.authorize()

// Do any additional setup after loading the view, typically from a nib.
    let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
//        layout.sectionInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
    layout.itemSize = CGSize(width: 124, height: 124)
    layout.minimumInteritemSpacing = 1.0
    layout.minimumLineSpacing = 1.0
    collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
    collectionView!.dataSource = self
    collectionView!.delegate = self
    collectionView!.registerClass(Cell.self, forCellWithReuseIdentifier: "Cell")
    collectionView!.backgroundColor = UIColor.whiteColor()
    self.view.addSubview(collectionView!)

    getData()
}

    func getData() -> Void {
        let baseUrl = NSURL(string:"https://api.instagram.com/v1/users/7522782/media/recent/?access_token=\(self.accessToken)")

//        let forcastUrl = NSURL(string: "37.8267,-122.423", relativeToURL: baseUrl)

        //        let data = NSData(contentsOfURL: forcastUrl)
        //        println(data)
        let sharedSession = NSURLSession.sharedSession()
        let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(baseUrl!, completionHandler: { (location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in


            //            var urlContents = NSString.stringWithContentsOfURL(location, encoding: NSUTF8StringEncoding, error: nil)
            //            println(urlContents)

            let dataObject = NSData(contentsOfURL: baseUrl!)

            if (error == nil) {
                let responseDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataObject!, options: nil, error: nil) as NSDictionary
//                println(responseDictionary)

                var currentResponse = responseDictionary.valueForKeyPath("data.images.standard_resolution.url") as NSArray



                dispatch_async(dispatch_get_main_queue(), { () -> Void in

//                    for url in currentResponse {
//                        var urlStrings: NSString = url as NSString
//                        
//                        var images =  UIImage(data: NSData(contentsOfURL: NSURL(string:urlStrings)!)!)
//
//                    }
                    for url in currentResponse {
                        var urls: NSString = url as NSString
                        //                        println(images)
                        var photoUrls = NSURL(string: urls)

                        var err: NSError?
                        var imageData :NSData = NSData(contentsOfURL: photoUrls!,options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &err)!
                        self.photos.addObject(UIImage(data:imageData)!)
                        println(self.photos)
                    }

                    self.photoCount = currentResponse.count as Int

                    self.collectionView?.reloadData()

                })

            } else {

                let networkIssueController = UIAlertController(title: "Error", message: "Something went wrong get a better phone you pleb!", preferredStyle: .ActionSheet)
                let okButton = UIAlertAction(title: "OK", style: .Default, handler: nil)
                networkIssueController.addAction(okButton)
                let cancelButton = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
                networkIssueController.addAction(cancelButton)

                self.presentViewController(networkIssueController, animated: true, completion: nil)

                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    //Stop refresh animation


                })
            }
        })

        downloadTask.resume()

    }


override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return photoCount

}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as Cell
//                println(photos)
//            cell.textLabel.text = "Text"
    cell.imageView.image = photos.objectAtIndex(indexPath.row) as? UIImage
//    cell.photo = self.photos[indexPath.row] as? NSDictionary
    cell.imageView.backgroundColor = colorWheel.randomColor()

    return cell
}
}

As you can see I have a NSURLDownload session that downloads info off instagram from user "7522782". I parse the data and count the "currentResponse" array to get the number of cells I need in my collection view. Then I parse the info convert urls to UIimages and put them in the cells.

How do I do this for more then one user. I want to show photos from 20+ users.
bear in mind that i'll need to sum up the total number photos from all users to tell my collection view how many cells to make.

Thanks in advance.


Solution

  • This is one way I thought right now...

    first of all fix the way you are using dispatch... A Good Answer about Dispatch Queues

    //Might run into an issue that if you insert more heavy logic here your UI will get stuck until processing ends.
    dispatch_async(dispatch_get_main_queue(), { () -> Void in }
    

    So use dispatch_get_global_queue instead for background threading and then Call dispatch_get_main_queue when you want to run code that will update your UI inside the Background closure(Block).

    //Correct way to use it to do Background work.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
    
       //async code the callings to the NSUrlSession any other logic
    
           dispatch_async(dispatch_get_main_queue(), { () -> Void in 
              //Do UI Updates Code.
           }
    
    } 
    

    Now for your specific situation. You are correctly handling the user getting all the urls for the Photos for that user, so now add something like this to handle multiple users for the same screen. Problem it is going to take a long time to download 20+ users photos, but since you are doing it async the screen wont get stuck.

    Do a Matrix, an Array of arrays, of the photos; each array inside the main array is the set of photos per User.

    var photoUserMatrix = [NSMutableArray]() //using Swift array and NSMutableArray
    

    Or use this since you are more inclined to NSMutableArrays

    var photoUserMatrix = NSMutableArray()   
    photoUserMatrix.addObject(NSMutableArray())//Each Photo Set per user is a new array.
    

    Problem with the Matrix is that finding something is O(N^2) in execution Time.

    Anyways now you can have X number of Users and your code will handle it. by using this piece of code.

    //Use this pseudo code to start downloading the multiple pictures for the users. 
    for i in 0...countOfUsers{
    
       //Create the array of Photos for this user. 
       var newPhotoSet = NSMutableArray()
    
       //Now use your code that downloads the photos with 
       //the Urls given for that users photos. with another loop on the Count of Urls/Photos
       for pos in 0...countOfUrls{
    
             //Download the Image with NSUrlSession
    
             //and add the photo to the new Array in the CompletionHandler
             newPhotoSet.AddObject(newImageDownloaded);
       }
    
       photoUserMatrix.addObject(newPhotoSet)
    
    
    }
    

    Code to display CollectionViewCells.

    for photoSet in photoUserMatrix as (NSMutableArray) {
    
        var count:Int = photoSet.count
        for i in 0...count{
            //use algorithm to display downloaded pictures in CollectionView. 
            //If any image is nil do not display that image. 
           //Same Code can be used to do the sum of all photos in each array. 
        }
    
    
    }
    

    Keep in mind that this is not the most optimal solution. Also the design of the application doesn't help, doing a load Images for 20+ users in the same screen is kind of an issue you will run there unless the images are really really small.

    Also I would suggest you add this working logic code into a method, that will call the Dispatch_get_global_queue for each User that requires to download images, making it more concurrent.

    PS: if you hit any issues tell me I will gladly help.