laser2302
laser2302

Reputation: 437

Issue with Asynchronous loading of images in uiimage array in SWIFT

I want to asynchronously load images to a UIImage which is defined as:

var albumImages = [UIImage]()

But it seems the images are not being fetched properly and shows lot of lag. Maybe I'm writing the code in an incorrect way, can anyone please help me look into the code and help me see which step I'm doing wrong? I'm really new to swift so really need help, thanks. Following is my code, what I'm doing is first fetching the URLs of Images and then putting these URLS in an array called albumImagePathArray and then running a for loop to put load these images in albumImages UIImage array:

let showMerchantInfoUrl=NSURL(string:"http://www.someurlhere.com")
let showMerchantInfoRequest=NSMutableURLRequest(URL:showMerchantInfoUrl!)
showMerchantInfoRequest.HTTPMethod="POST"

let showMerchantInfoString="token=\(KeychainWrapper.stringForKey("tokenValue")!)";
showMerchantInfoRequest.HTTPBody=showMerchantInfoString.dataUsingEncoding(NSUTF8StringEncoding)

//send the request
let task=NSURLSession.sharedSession().dataTaskWithRequest(showMerchantInfoRequest) {
    data, response, error in

    if error != nil {
        print("error=\(error)")
        return
    }

    do {
        let json=try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary

        if let parseJSON=json {
            let codeValue = parseJSON["code"] as? Int
            let msgValue = parseJSON["msg"] as? String

            if codeValue! == 1 {
                print(json)
                dispatch_async(dispatch_get_main_queue(), {
                    if let retDictionary = parseJSON["ret"]!["info"] as? NSDictionary {
                        self.shopName.text = retDictionary.valueForKey("mer_name") as? String
                        if retDictionary.valueForKey("mer_name") as? String != nil {
                        KeychainWrapper.setString(self.shopName.text!, forKey: "shopName")
                        self.totalCouponsTaken.text = String(self.nullToZero(retDictionary.valueForKey("visits"))!) + "人"
                        self.unusedCoupons.text = String(self.nullToZero(retDictionary.valueForKey("coupon_not_used_yet"))!) + "人"
                        self.totalCouponsTaken.text = String(self.nullToZero(retDictionary.valueForKey("coupon_taken_total_numbers"))!) + "人"
                        self.merchantRatingCV.rating = Double(self.nullToZero(retDictionary.valueForKey("star"))! as! NSNumber)

                        KeychainWrapper.setString(String(self.nullToZero(retDictionary.valueForKey("star"))!), forKey: "shopRating")

                        self.merchantRatingCV.text = String(self.nullToZero(retDictionary.valueForKey("star"))!)

                        }
                    }
                    if let retAlbumArray = parseJSON["ret"]!["album"] as? NSArray {
                        print("REEET :\(retAlbumArray)")

                        self.albumImagePathArray = retAlbumArray.flatMap({ element in
                            (element["mer_thumb"] as? String)!
                        })

                        self.albumImageID = retAlbumArray.flatMap({ element in (element["album_id"] as? String)! })

                        self.coverableArray = retAlbumArray.flatMap({ element in
                            (element["coverable"] as? String)!
                        })

                        for i in 0 ..< self.albumImagePathArray.count {
                            let request: NSURLRequest = NSURLRequest(URL: NSURL(string: self.albumImagePathArray[i])!)
                            let mainQueue = NSOperationQueue.mainQueue()

                            NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
                                print("asdfasdfasdfasfdasfasdf")
                            if error == nil {
                                let image = UIImage(data: data!)
                                self.albumImages.append(image!)
                                }
                            else { print(error) }
                                })
                        }

                        if self.albumImages.count >= self.maximumImagesAllowed {
                            disableAdditionOfImages=true
                        }
                        else {
                            self.albumImages.append(UIImage(named:"Plus.png")!)
                        }

                        if self.disableAdditionOfAlbumImages == false && self.albumImages.count > 1 {
                            let bannerPhoto=self.albumImages[0]
                            self.bannerImage.image = bannerPhoto
                        }

                        UIView.performWithoutAnimation {
                            self.albumCollectionView?.reloadData()
                            self.activeIndicator.stopAnimating()
                        }
                        if self.albumImagePathArray==[] {
                            self.activeIndicator.stopAnimating()
                        }
                    }
                    else {
                        self.activeIndicator.stopAnimating()
                    }

                    if codeValue!==1
                    {
                        print(json)
                    }
                    else
                    {
                        self.showSimpleAlert(msgValue!, message: nil, segueToLogin: false)
                    }
                })
            }
            else {
                self.showSimpleAlert(msgValue!, message: nil, segueToLogin: true)
            }
        }
    }
    catch let err {
        print(err)
    }
}
task.resume();

Upvotes: 1

Views: 499

Answers (1)

Wain
Wain

Reputation: 119031

You should separate your code to make it more readable / understandable. That means putting different functionality in different functions and calling those functions from completion blocks. So you'd have one which starts the first request, one which handles the response and another which handles the images.

But, for the images you should also change your approach more drastically. Starting an arbitrary number of image downloads all at the same time will flood the network and result in timeouts. There is also no guarantee that the images would be added to the array in the same order as the urls because they will each take a different amount of time to download.

Your current code is also a synchronously downloading the images but the code is written as if the download is synchronous (immediate) so all the logic about the image counts is invalid, because the array will always be empty by the time you get to the end of the loop.

Ideally you should only download the images when you need them, one or a few at a time, cancelling any downloads where the user didn't care enough to wait for the image to load (and scrolled past or left the view). You can manage that yourself, or you can use a library for it, like SDWebImage or AlamoFireImage.

A simple, if not ideal, way to manage it yourself would be to create download operations and add them to a new operation queue. You can add another operation to the queue which is dependent on all the other operations finishing which has any completion logic.

A better way means reimplementing the libraries to start, track and cache each download separately.

Neither of these options has your image counting logic, but you can actually just do that with a counter in your loop and not store extra urls if you don't need them...

Upvotes: 1

Related Questions