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.
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.
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()
}
}
}