0

I'm new to iOS development, so any help would be appreciated.

The project: I am developing an Emoji-style keyboard for iOS. When the keyboard first loads it downloads a .txt file from the Internet with image URLs (this way I can add/delete/reorder the images simply by updating that .txt file). The image URLs are entered into an Array, then the array of image URLs is used to populate a collection view.

The code I have written works, but when I switch to my keyboard the system waits until everything is loaded (typically 2-3 seconds) before the keyboard pops up. What I want is for the keyboard to popup instantly without images, then load the images as they become available - this way the user can see the keyboard working.

How can I get the keyboard to load instantly, then work on downloading the text file followed by the images? I am finding a lot of information on asynchronous downloading for images, but my problem is a little more complex with the addition of the text download and I can't quite figure it out.

more info:

I'm using SDWebImage to load the images so they are caching nicely and should be loading asynchronously, but the way I have it wrapped up the keyboard is waiting until everything is downloaded to display.

I tried wrapping the "do { } catch { }" portion of the code below inside - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { //do-catch code }) - when I do this the keyboard pops up instantly like I want, but instead of 2-3 seconds to load the images, it takes 8-10 seconds and it still waits for all images before showing any images.

Below is my code. I simplified the code by removing buttons and other code that is working. There are two .xib files associated with this code: KeyboardView and ImageCollectionViewCell

import UIKit
import MobileCoreServices
import SDWebImage


class ImageCollectionViewCell:UICollectionViewCell {
    @IBOutlet var imgView:UIImageView!
}

class KeyboardViewController: UIInputViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    @IBOutlet var collectionView: UICollectionView!

    let blueMojiUrl = NSURL(string: "http://example.com/list/BlueMojiList.txt")
    var blueMojiArray = NSMutableArray()

    func isOpenAccessGranted() -> Bool {
        // Function to test if Open Access is granted
        return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
    }


    override func viewDidLoad() {
        super.viewDidLoad()

        let nib = UINib(nibName: "KeyboardView", bundle: nil)
        let objects = nib.instantiateWithOwner(self, options: nil)
        self.view = objects[0] as! UIView;

        self.collectionView.registerNib(UINib(nibName: "ImageCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "ImageCollectionViewCell")


        if (isOpenAccessGranted()) == false {
            // Open Access is NOT granted - display error instructions

        } else {
            // Open Access IS granted

                do {

                self.blueMojiArray = NSMutableArray(array: try NSString(contentsOfURL: self.blueMojiUrl!, usedEncoding: nil).componentsSeparatedByString("\n"));

                self.collectionView.reloadData()

            } catch {
                // Error connecting to server - display error instructions
            }

        }

    } // End ViewDidLoad


    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.blueMojiArray.count
    }


    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let iCell = collectionView.dequeueReusableCellWithReuseIdentifier("ImageCollectionViewCell", forIndexPath: indexPath) as! ImageCollectionViewCell
        let url = NSURL(string: self.blueMojiArray.objectAtIndex(indexPath.row) as! String)

        iCell.imgView.sd_setImageWithURL(url, placeholderImage: UIImage(named: "loading"))

        iCell.layer.cornerRadius = 10.0

        return iCell
    }

}

UPDATE: Thanks for the answers. I was able to fix my problem with Alamofire. I installed the Alamofire pod to my project. The specific code I used to fix my issue: first I added import Alamofire at the top, then I removed the do { } catch { } code above and replaced it with

Alamofire.request(.GET, blueMojiUrl!)
                .validate()
                .responseString { response in

                    switch response.result {

                    case .Success:
                        let contents = try? NSString(contentsOfURL: self.blueMojiUrl!, usedEncoding: nil)
                        self.blueMojiArray = NSMutableArray(array: contents!.componentsSeparatedByString("\n"))

                    self.collectionView.reloadData()


                    case .Failure:
                        // Error connecting to server - display error instructions
                    }
            }

Now the keyboard loads instantly, followed by the images loading within 2-3 seconds.

iOS_Mouse
  • 754
  • 7
  • 13

2 Answers2

0

I would do something like that:

On cellForItemAtIndexPath call method to download image for current cell with completion handler. On download completion set cell's image to downloaded image and call method reloadItemsAtIndexPaths for this cell.

Example code:

func downloadPlaceholderImage(url: String, completionHandler: (image: UIImage) -> ()) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        /// download logic
           completionHandler(image: downloadedImage)
        })            
    }
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
   ///
   downloadPlaceHolderImage("url", completionHandler: { image in
     cell.image = image 
     self.collectionView.reloadItemsAtIndexPaths([indexPath])
   })
  ///  
}
  • Thanks! In my case using reloadItemsAtIndexPaths caused problems because I have several collections, so they got stacked on top of each other :) I was able to get it working properly with Alamofire, and I updated my question with my results. Thanks for the response, I appreciate you taking the time to help. – iOS_Mouse Aug 24 '16 at 22:35
-1

The recommend way to do this after iOS 8 is with NSURLSession.dataTaskWithURL

See: sendAsynchronousRequest was deprecated in iOS 9, How to alter code to fix

Community
  • 1
  • 1
Lightbeard
  • 4,011
  • 10
  • 49
  • 59
  • Thanks! This helped point me in the right direction, but I was unable to implement it correctly on my own. I finally got the results I wanted using Alamofire (which I believe uses NSURLSession), and I updated my question with my results. – iOS_Mouse Aug 24 '16 at 22:32