iosswiftuicollectionviewios-lifecycle

Populating UICollectionView from Online Database


I'm creating a simple chat app, it has a loading screen with a segue to either the login screen if the user is not logged in or directly to his chats if he is. The chats are displayed in a UICollectionView. When I was first testing, I populated it with dummy data which I declared in the class itself, and everything worked fine. Now I am fetching the user's chats from an online database in the Loading Screen, and storing them in an array called user_chats which is declared globally.

enter image description here

I use the following code to populate the UICollectionView :

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
   // getUserChats()
    return user_chats.count

}

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

let cell = collectionView.dequeueReusableCellWithReuseIdentifier("chat_cell" , forIndexPath: indexPath) as! SingleChat

cell.chatName?.text = user_chats[indexPath.row].chat_partner!.name
cell.chatTextPreview?.text = user_chats[indexPath.row].chat_messages!.last!.text
let profile_pic_URL = NSURL(string : user_chats[indexPath.row].chat_partner!.profile_pic!)
downloadImage(profile_pic_URL!, imageView: cell.chatProfilePic)
cell.chatProfilePic.layer.cornerRadius = 26.5
cell.chatProfilePic.layer.masksToBounds = true

    let dividerLineView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor(white: 0.5, alpha: 0.5)
        return view
    }()
    dividerLineView.translatesAutoresizingMaskIntoConstraints = false

    cell.addSubview(dividerLineView)
    cell.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-1-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))
    cell.addSubview(dividerLineView)
    cell.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[v0(1)]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))
    return cell

}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

    self.performSegueWithIdentifier("showChat", sender: self)
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

    if (segue.identifier == "showChat") {

        let IndexPaths = self.collectionView!.indexPathsForSelectedItems()!
        let IndexPath = IndexPaths[0] as NSIndexPath
        let vc = segue.destinationViewController as! SingleChatFull

        vc.title = user_chats[IndexPath.row].chat_partner!.name
    }

}

DATA FETCH :

func getUserChats() {
    let scriptUrl = "*****"
    let userID = self.defaults.stringForKey("userId")
    let params = "user_id=" + userID!
    let myUrl = NSURL(string: scriptUrl);
    let request: NSMutableURLRequest = NSMutableURLRequest(URL: myUrl!)
    request.HTTPMethod = "POST"
    let data = params.dataUsingEncoding(NSUTF8StringEncoding)
    request.timeoutInterval = 10
    request.HTTPBody=data
    request.HTTPShouldHandleCookies=false
    UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    let queue:NSOperationQueue = NSOperationQueue()
    NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
        do {
            if (data != nil) {
                do {
                    var dataString = String(data: data!, encoding: NSUTF8StringEncoding)
                    var delimiter = "]"
                    var token = dataString!.componentsSeparatedByString(delimiter)
                    dataString = token[0] + "]"
                    print(dataString)
                    let data_fixed = dataString!.dataUsingEncoding(NSUTF8StringEncoding)
                    do {
                        let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])


                        // LOOP THROUGH JSON ARRAY AND FETCH VALUES
                        for anItem in jsonArray as! [Dictionary<String, AnyObject>] {
                            let curr_chat = Chat()
                            if let chatId = anItem["chatId"] as? String {
                                curr_chat.id = chatId
                            }
                            let friend = Friend()
                            let user1id = anItem["user1_id"] as! String
                            let user2id = anItem["user2_id"] as! String
                            if (user1id == userID) {
                                if let user2id = anItem["user2_id"] as? String {
                                    friend.id = user2id
                                }
                                if let user2name = anItem["user2_name"] as? String {
                                    friend.name = user2name
                                }
                                if let user2profilepic = anItem["user2_profile_pic"] as? String {
                                    friend.profile_pic = user2profilepic
                                }
                            }
                            else if (user2id == userID){
                                if let user1id = anItem["user1_id"] as? String {
                                    friend.id = user1id
                                }
                                if let user1name = anItem["user1_name"] as? String {
                                    friend.name = user1name
                                }
                                if let user1profilepic = anItem["user1_profile_pic"] as? String {
                                    friend.profile_pic = user1profilepic
                                }
                            }

                            curr_chat.chat_partner = friend

                            var chat_messages = [Message]()
                            if let dataArray = anItem["message"] as? [String : AnyObject] {
                                for (_, messageDictionary) in dataArray {
                                    if let onemessage = messageDictionary as? [String : AnyObject] {                                        let curr_message = Message()
                                        if let messageid = onemessage["message_id"] as? String {
                                            curr_message.id =  messageid
                                        }
                                        if let messagedate = onemessage["timestamp"] as? String {
                                            let dateFormatter = NSDateFormatter()
                                            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                                            let date = dateFormatter.dateFromString(messagedate)
                                            curr_message.date = date
                                        }
                                        if let messagesender = onemessage["sender"] as? String {

                                            curr_message.sender = messagesender
                                        }
                                        if let messagetext = onemessage["text"] as? String {
                                            curr_message.text = messagetext
                                        }
                                        chat_messages.append(curr_message)
                                    }}
                            }

                            curr_chat.chat_messages = chat_messages
                            user_chats.append(curr_chat)
                        }

                    }
                    catch {
                        print("Error: \(error)")
                    }
                }
//                       NSUserDefaults.standardUserDefaults().setObject(user_chats, forKey: "userChats")

            }
            else {
                dispatch_async(dispatch_get_main_queue(), {
                    let uiAlert = UIAlertController(title: "No Internet Connection", message: "Please check your internet connection.", preferredStyle: UIAlertControllerStyle.Alert)

                    uiAlert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { action in
                        self.dismissViewControllerAnimated(true, completion:nil)
                    }))
                    self.presentViewController(uiAlert, animated: true, completion: nil)
                })
            }

        } catch _ {
            NSLog("error")
        }

    })
}

The problem is that the collection view is always empty now. I have done some debugging and put a breakpoint inside the first function, and I saw that this method is called when the Loading Screen is still displayed to the user and the chat screen hasn't even been loaded. My suspicion is that this is called before the data is fetched from the internet in the Loading Screen, and as a result the size of the user_chats array is 0. I am used to working with Android and ListView where the ListView are never populated until the parent view is displayed on screen, hence why I am confused. The method which fetches the data from the online database works fine as I have already extensively debugged it, so the problem isn't there.


Solution

  • The best option is to add a completionHandler to your function to be notified when the data is return and/or when the async function is finished executing. The code below is a truncated version of your getUserCharts function with a completionHandler, which returns a true or false when the data is load (You could modify this to return anything you wish). You can read more about closures/ completion handlers https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html or google.

    function

    func getUserChats(completionHandler: (loaded: Bool, dataNil: Bool) -> ()) -> (){
            NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
                do {
                    if (data != nil) {
                        do {
                            var dataString = String(data: data!, encoding: NSUTF8StringEncoding)
                            var delimiter = "]"
                            var token = dataString!.componentsSeparatedByString(delimiter)
                            dataString = token[0] + "]"
                            print(dataString)
                            let data_fixed = dataString!.dataUsingEncoding(NSUTF8StringEncoding)
                            do {
                                let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])
    
                                // LOOP THROUGH JSON ARRAY AND FETCH VALUES
                                completionHandler(loaded: true, dataNil: false)
                            }
                            catch {
                                print("Error: \(error)")
                            }
                        }
                    }
                    else {
                        //Handle error or whatever you wish
                        completionHandler(loaded: true, dataNil: true)
                    }
    
                } catch _ {
                    NSLog("error")
                }
    

    How to use it

    override func viewDidLoad() {
            getUserChats(){
                status in
                if status.loaded == true && status.dataNil == false{
                    self.collectionView?.reloadData()
                }
            }
        }