Populating UICollectionView from Online Database - ios

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

It sounds like this is an async issue. I'm not sure how your project is setup but you need to call reloadData() on your collection view when the response is returned.

After you have received the data back from the server, and updated the data source for the collection view you need to refresh the collection view (Make sure you are on the main thread, since it is modifying the UI):
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
}
Edit:
Also, I'm not completely sure how you have your project setup, but you could create a delegate for your data fetch, so every time you get something back from the server it calls a delegate method that there are new messages. Your collection view controller would subscribe to that delegate, and every time the that method is called it would reload your collection view.
The Delegate:
protocol ChatsDelegate {
func didUpdateChats(chatsArray: NSArray)
}
In your Data Fetch:
user_chats.append(cur_chat)
self.delegate.didUpdateChats(user_chats)
In your collectionView controller:
class viewController: ChatsDelegate, ... {
...
func didUpdateChats(chatsArray: NSArray) {
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
}
}

Related

I am trying to loop over an array of strings and firing api calls to reload the collection view

import UIKit
import GooglePlaces
import Alamofire
import CoreData
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listData?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CityCollectionViewCell
let city = listData![indexPath.row] as? NSDictionary
let name = city?.object(forKey: "name") as? String
let main = city?.object(forKey: "main") as! NSDictionary
let temp = main.object(forKey: "temp") as? Double
let date1 = city?.object(forKey: "dt")
let date = Date(timeIntervalSince1970: date1 as! TimeInterval)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "GMT") //Set timezone that you want
dateFormatter.locale = NSLocale.current
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm" //Specify your format that you want
let strDate = dateFormatter.string(from: date)
cell.cityLabel.text = name!
cell.lastUpdatedLabel.text = strDate
cell.tempLabel.text = "\(temp!)"
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
cv.deselectItem(at: indexPath, animated: true)
let row = indexPath.row;
let selectedCity = list![row];
userDefaults?.set(selectedCity, forKey: "citySelection");
self.performSegue(withIdentifier: "selectCity", sender: self);
}
#IBOutlet weak var cv: UICollectionView!
var userDefaults:UserDefaults?;
var list:NSMutableArray?
var listData:NSMutableArray?
let group = DispatchGroup()
override func viewDidLoad() {
super.viewDidLoad()
userDefaults = UserDefaults.standard;
}
#IBAction func addCity(_ sender: Any) {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
let addressFilter = GMSAutocompleteFilter()
addressFilter.type = .city
autocompleteController.autocompleteFilter = addressFilter
present(autocompleteController, animated: true, completion: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateValues()
}
func updateValues() {
let list = getSearchHistory()
print(list)
let count = list.count
if count > 0
{
for item in list {
group.enter()
getData(name: item as! String)
}
group.notify(queue: .main, execute: {
self.cv.reloadData()
})
}
}
func getData(name: String) {
let modified = name.replacingOccurrences(of: " ", with: "+")
let url = "http://api.openweathermap.org/data/2.5/weather?q=\(modified)&APPID=-------"
Alamofire.request(url, method: HTTPMethod.get).responseJSON(completionHandler: {
(response) -> Void in
let city = response.result.value as! NSDictionary;
self.listData?.add(city)
print(self.listData)
self.group.leave()
})
}
func addToSearchHistory(locationName:String) {
let delegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = delegate.persistentContainer.viewContext;
let entity = NSEntityDescription.insertNewObject(forEntityName: "SavedPlaces", into: managedContext)
entity.setValue(locationName, forKey: "name")
do {
try managedContext.save();
}
catch {
print("Core data error");
}
}
func getSearchHistory() -> NSMutableArray {
let returnData = NSMutableArray()
let delegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = delegate.persistentContainer.viewContext;
do {
let req = NSFetchRequest<NSFetchRequestResult>(entityName: "SavedPlaces");
let data = try managedContext.fetch(req) as! [NSManagedObject];
for item in data {
let name = item.value(forKey: "name") as? String;
returnData.add(name!);
}
}
catch {
print("Core data error");
}
return returnData;
}
}
extension ViewController: GMSAutocompleteViewControllerDelegate {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
self.addToSearchHistory(locationName: place.name)
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
I am trying to load the values from stored data using core Data, and then looping over strings and calling the api and then adding it to a new array from which I am populating the collection view.
Problem: I am having all the values(Cities names) list populated correctly but after calling the api and call the "UpdateUI" function I am getting only one cell.
gif file
Requests are not yet completed this is an asynchronous task wait i will handle the code for you , this way whenever a new response come that tableView will be reloaded to reflect that
func updateValues() {
let list = getSearchHistory()
if !list.isEmpty
{
for item in list {
getData(name: item as! String)
}
}
}
func getData(name: String) {
let modified = name.replacingOccurrences(of: " ", with: "+")
let rr = NSMutableArray()
let url = "http://api.openweathermap.org/data/2.5/weather?q=\(modified)&APPID=------------------"
Alamofire.request(url, method: HTTPMethod.get).responseJSON(completionHandler: {
(response) -> Void in
let city = response.result.value as! NSDictionary;
rr.add(city)
self.listData.append(rr)
DispatchQueue.main.async
{
self.cv.reloadData()
}
})
}
Also note a very important step in response you overwrite current array not append to it
self.listData = rr
it should be
self.listData.append(rr)
and that causes the permanent display of one item whether a load occurs or not
Also don't forget to initalize listData in viewDidLoad
listData = NSMutableArray()
Try to parse the api like this
-(void)getDataforCity:(NSString*)cityName
{
NSURL*url = [NSURL URLWithString:[NSString stringWithFormat:#"%#?APPID=%#&q=%#",openWeatherMapBaseURL,openWeatherMapAPIKey,cityName]];
[NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error == nil)
{
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSDictionary*main = json[#"main"];
NSString*humidity = main[#"humidity"];
NSString*pressure = main[#"pressure"];
NSString*temp = main[#"temp"];
NSString*tempMax = main[#"temp_max"];
NSString*tempMin = main[#"temp_min"];
NSArray*weatherArr = json[#"weather"];
NSDictionary*weather = weatherArr[0];
NSString*description = weather[#"description"];
NSDictionary*wind = json[#"wind"];
NSString*deg = wind[#"deg"];
NSString*speed = wind[#"speed"];
NSLog(#"humidity %# : ",humidity);
NSLog(#"pressure %# : ",pressure);
NSLog(#"temp %# : ",temp);
NSLog(#"tempMax %# : ",tempMax);
NSLog(#"tempMin %# : ",tempMin);
NSLog(#"description %# : ",description);
NSLog(#"deg %# : ",deg);
NSLog(#"speed %# : ",speed);
NSLog(#"dasdasddasdataioioio : %#",json);
}
else
{
NSLog(#"dasdasddasdata : %#",error);
}
}].resume;
}
You can reload the collectionView after each request completes by calling reloadData inside your networking callback, though that's a bit inefficient unless you really need to reload after each item. If you want to wait until all data is loaded before reloading your collectionView you may consider using a DispatchGroup. That could work like this:
let dispatchGroup = DispatchGroup()
func updateValues() {
let list = getSearchHistory()
let count = list.count
if count > 0 {
for item in list {
dispatchGroup.enter() //Indicate that a new process is beginning in the dispatch group
getData(name: item as! String)
}
group.notify(queue: .main) { //When all processes in the group finish this code will execute
self.cv.reloadData()
}
}
}
func getData(name: String) {
let modified = name.replacingOccurrences(of: " ", with: "+")
let url = "http://api.openweathermap.org/data/2.5/weather?q=\(modified)&APPID=------------------"
Alamofire.request(url, method: HTTPMethod.get).responseJSON(completionHandler: { (response) -> Void in
let city = response.result.value as! NSDictionary
self.listData.append(rr)
dispatchGroup.leave() //Indicate that a process is ending in the dispatch group
})
}

UITableCell value not passing to function within UIViewController Swift 3

I have a table that is populated by a search function. There are two buttons within the cell, a checkmark to say yes to a user and an X to say no. There is an insert function that inserts the selection into the database. Unfortunately the value from the table is not being passed to the insert function. Within the insert function, I'm using guestusername.text which is the name of the label in my cell. I'm getting the error 'Use of unresolved identifier guestusername'. I've tried everything I can think of, code below.
class MyShotsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var guest = [AnyObject]()
var avas = [UIImage]()
var valueToPass:String!
var revieweduser:String!
var age = [AnyObject]()
var city = [AnyObject]()
var state = [AnyObject]()
#IBOutlet var tableView: UITableView!
var cell: MyShotsCell?
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
doSearch("")
}
// cell numb
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return guest.count
}
// cell config
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyShotsCell
// get one by one user related inf from users var
let guest2 = guest[indexPath.row]
let ava = avas[indexPath.row]
// shortcuts
let guestname = guest2["username"] as? AnyObject
let age = guest2["age"]
let city = guest2["city"] as? String
let state = guest2["state"] as? String
// refer str to cell obj
cell.guestusername.text = guestname as! String
cell.ageLbl.text = (NSString(format: "%#", age as! CVarArg) as String)
cell.cityLbl.text = city
cell.stateLbl.text = state
cell.avaImg.image = ava as? UIImage
return cell
}
// search / retrieve users
public func doSearch(_ guestusername : String) {
// shortcuts
let username = user?["username"] as! String
let url = URL(string: "http://www.xxxxx.com/xxxxx.php")!
var request = URLRequest(url: url) // create request to work with users.php file
request.httpMethod = "POST" // method of passing inf to users.php
let body = "revieweduser=\(username)" // body that passes inf to users.php
request.httpBody = body.data(using: .utf8) // convert str to utf8 str - supports all languages
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// getting main queue of proceeding inf to communicate back, in another way it will do it in background
// and user will no see changes :)
DispatchQueue.main.async(execute: {
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// clean up
self.guest.removeAll(keepingCapacity: false)
self.avas.removeAll(keepingCapacity: false)
self.tableView.reloadData()
// delcare new secure var to store json
guard let parseJSON = json else {
print("Error while parsing")
return
}
guard let parseUSERS = parseJSON["users"] else {
print(parseJSON["message"] ?? [NSDictionary]())
return
}
self.guest = parseUSERS as! [AnyObject]
print(self.guest)
// for i=0; i < users.count; i++
for i in 0 ..< self.guest.count {
// getting path to ava file of user
let ava = self.guest[i]["ava"] as? String
let revieweduser = self.guest[i]["username"] as? String
let age = (NSString(format: "%#", self.guest[i]["age"] as! CVarArg) as String)
let city = self.guest[i]["city"] as? String
let state = self.guest[i]["state"] as? String
self.tableView.reloadData()
} catch {
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
} .resume()
}
// custom body of HTTP request to upload image file
func createBodyWithParams(_ parameters: [String: String]?, boundary: String) -> Data {
let body = NSMutableData();
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
return body as Data
}
func insertShot(_ rating : String) {
self.tableView.reloadData()
let reviewer = user?["username"] as! String
// url path to php file
let url = URL(string: "http://www.xxxxxx.com/xxxxxxx.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// param to be passed to php file
let param = [
"user" : reviewer,
"revieweduser" : cell?.guestusername.text,
"rating" : rating
] as [String : Any]
// body
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
// ... body
request.httpBody = createBodyWithParams(param as? [String : String], boundary: boundary)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// get main queu to communicate back to user
DispatchQueue.main.async(execute: {
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// declare new var to store json inf
guard let parseJSON = json else {
print("Error while parsing")
return
}
// get message from $returnArray["message"]
let message = parseJSON["message"]
//print(message)
// if there is some message - post is made
if message != nil {
// reset UI
// self.msgTxt.text = ""
// switch to another scene
//self.tabBarController?.selectedIndex = 3
_ = self.navigationController?.popViewController(animated: true)
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
}.resume()
return
}
#IBAction func yesBtn_clicked(_ sender: UIButton) {
self.insertShot("Yes")
}
#IBAction func noBtn_clicked(_ sender: UIButton) {
self.insertShot("No")
}
}

I can not get the json data to display in my second UIViewController

I have the following two functions in my first ViewController. They load a UITableView with over 300 rows. I call the loadRemoteData function inside the ViewDidLoad. Everything works fine in the first ViewController.
// MARK: - parseJSON
func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject], rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
let eachLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
legislators.append(eachLegislator)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData() {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = "https://somedomain.com/legislators?order=state_name__asc,last_name__asc&fields=first_name,last_name,bioguide_id"
if let url = NSURL(string: url) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
In the second ViewController, I want to display more information about the individual listed in the cell that is tapped, for that I use a different URL such as https://somedomain.com/legislators?bioguide_id=\"\(bioguideId)\" which provides me with a lot more detail. (The data being requested from the JSON Dictionary is different)
The code I use in the second ViewController is just like shown above with the only difference being the URL. I can print the url coming from the previous ViewController and it is displayed in the console log but no json data is shown.
I would appreciate any help.
Thanks
Below is the code for my second ViewController:
import UIKit
class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var bioguideId: String?
var currentLegislator: Legislator? = nil
var currentLegislatorUrl: String?
let reuseIdentifier = "Cell"
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var tableView: UITableView!
// MARK: - parseJSON
private func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject],
rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
currentLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData(url: String) {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = currentLegislatorUrl
if let url = NSURL(string: url!) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
print("Success")
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
func loadImage(urlString:String) {
let imgURL: NSURL = NSURL(string: urlString)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
self.imageView.image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue(), display_image)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
print(currentLegislatorUrl!)
loadRemoteData(currentLegislatorUrl!)
loadImage("https://theunitedstates.io/images/congress/225x275/\(bioguideId!).jpg")
self.title = bioguideId
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath)
cell.textLabel!.text = currentLegislator?.firstName
return cell
}
}
Thanks to Adam H. His comment made me reevaluate the URL I was using and by adding additional operators, now the data is shown in my second ViewController.

Swift - Manage tasks to populate UITableView

The view I'm developing does the following:
Sends a GET request to the API to retrieve a list of users
Sends GET requests to the API to retrieve profile images from the list of users
Display the images in TableViewCells
However, I'm having problem managing the tasks and the queues. What is the best way to be sure that all the requests and tasks are done before populating the Table View?
Here's the code:
import UIKit
class homeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var jsonData : [NSDictionary] = [NSDictionary]()
var imageUrls: NSDictionary = NSDictionary()
var urlsArray: [NSURL] = [NSURL]()
override func viewDidLoad() {
super.viewDidLoad()
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
self.refreshData()
self.getImage()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
})
}
override func viewWillAppear(animated: Bool) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jsonData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var type = jsonData[indexPath.row]["type"] as! Int
for typej in jsonData {
let t : Int = typej["type"] as! Int
println("type : \(t)")
}
if type == 1 {
let cell1 : cellTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! cellTableViewCell
/* //If images url are retrieved, load them. Otherwise, load the placeholders
if self.urlsArray.isEmpty == false {
println("Tiè: \(self.urlsArray[indexPath.row])")
if let data = NSData(contentsOfURL: self.urlsArray[indexPath.row]) {
cell1.profileImg?.image = UIImage(data: data)
}
} else {
cell1.profileImg?.image = UIImage(named: "placeholder.png")
}*/
let block: SDWebImageCompletionBlock! = {
(image: UIImage!, error: NSError!, cacheType: SDImageCacheType, imageURL: NSURL!) -> Void in
println(self)
}
println("url Array: \(self.urlsArray)")
let url = NSURL(string: "http://adall.ga/s/profile-1439584252497.png")
if UIApplication.sharedApplication().canOpenURL(urlsArray[indexPath.row]) {
cell1.profileImg.sd_setImageWithURL(urlsArray[indexPath.row], completed: block)
} else {
cell1.profileImg.sd_setImageWithURL(url, completed: block)
}
cell1.testLbl.text = (self.jsonData[indexPath.row]["author"] as? String)!
return cell1
} else {
let cell2 : cell2TableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell2") as! cell2TableViewCell
return cell2
}
}
func refreshData() {
let requestURL = NSURL(string:"http://adall.ga/api/feeds/author/mat/0")!
var request = NSMutableURLRequest(URL: requestURL)
request.HTTPMethod = "GET"
request.addValue(userToken, forHTTPHeaderField: "tb-token")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) {
data, response, error in
println(response)
var dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
println(dataString)
//let jsonResult : NSDictionary = (NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary)!
//jsonData = (NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers , error: nil) as? NSArray)!
self.jsonData = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: nil) as! [NSDictionary]
}
task.resume()
var index: Int
for index = 0; index < 10000; ++index {
print("Index: \(index), Task state: \(task.state)")
}
}
func getImage() {
var i = 0
for jsonSingleData in jsonData {
let author = jsonSingleData["author"] as! String
let requestURL2 = NSURL(string: "http://adall.ga/api/users/" + author + "/image")!
var request2 = NSMutableURLRequest(URL: requestURL2)
request2.HTTPMethod = "GET"
request2.addValue(userToken!, forHTTPHeaderField: "tb-token")
let session2 = NSURLSession.sharedSession()
let task2 = session2.dataTaskWithRequest(request2) {
data, response, error in
println("response= \(response)")
var dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
println(dataString)
self.imageUrls = (NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary)
if self.imageUrls["url"] != nil {
//check if exists
let imageUrl = self.imageUrls["url"] as! String
let url = NSURL(string: "http://" + imageUrl)
self.urlsArray.append(url!)
} else {
let imageUrl = "http://shackmanlab.org/wp-content/uploads/2013/07/person-placeholder.jpg"
let url = NSURL(string: imageUrl)
self.urlsArray.append(url!)
}
}
task2.resume()
self.tableView.reloadData()
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
The point of the issue is the following code:
dispatch_async(backgroundQueue, {
self.refreshData()
self.getImage()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
})
The NSURLSession working in the background thread, so your jsonData is empty when the self.getImage() and reloadData are executed.
You can call the self.getImage() after this line
self.jsonData = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: nil) as! [NSDictionary]
in the session.dataTaskWithRequest completed block and calls reloadData(on the dispatch_get_main_queue) in the completed block of the session2.dataTaskWithRequest.
I think this will solved your issue.

Tableviewcontroller "cellForRowAtIndexPath" - index starts from 4, not 0

I put three table view controllers on pageviewcontroller.
The pageviewcontroller loads middle vc.
When I go to the left vc, and pull table in order to reload - I have problem that on function cellForRowAtIndexPath the indexPath.row starts from 4, not 0.
Why does the indexPath.row start from 4 and not 0?
I think, it is not about code issue! I have error:
Cannot index empty buffer
I ve found the error. I dont know why does this happens but when I reload table (pulling it), for some reason, it takes the last row (in my case I have 4 rows on screen counting from 0) and uses it in cellAtIndex array. Every time before pulling data from internet I remove all elements from array. I changed that code, i did remove all elements before reloading and the error didnt appear. What's interesting I am using the same function on other vc and everything works.
I am making two request to download data:
class func JSONRequest2(urlInput: String, tableName: UITableView, action: (NSArray)->Void, refresh:UIRefreshControl, viewContr: UIViewController, hideLoadingViewAndStopAnimating: ()->Void) {
let urlPath = urlInput
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
println("started first json request")
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
if error != nil {
println(error)
let stringError = error.localizedDescription
CommonFunctions.showAlert("Ошибка", alertText: stringError, alertButtonText: "Закрыть", viewController: viewContr)
}
else{
var err: NSError?
if data != nil {
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSArray {
println("the number of news in json :\(jsonResult.count)")
if (err == nil) {
dispatch_async(dispatch_get_main_queue(), {
action(jsonResult)
//tableName.reloadData()
})
}else{
let stringError = err?.localizedDescription
CommonFunctions.showAlert("Ошибка", alertText: stringError!, alertButtonText: "Закрыть", viewController: viewContr)
}
}else {
hideLoadingViewAndStopAnimating()
println("json is not valid")
var dic = dictForErrors()
CommonFunctions.showAlert("Ошибка", alertText: dic.alertText, alertButtonText: "Закрыть", viewController: viewContr)
}
}else {
CommonFunctions.showAlert("Ошибка", alertText: "data is nil",alertButtonText: "Закрыть", viewController: viewContr)
println("json data is nil")
}
}
})
task.resume()
}
class func JSONRequest(urlInput: String, tableName: UITableView, action: (NSArray)->Void, refresh:UIRefreshControl, category: Int, viewContr: UIViewController, hideLoadingViewAndStopAnimating: ()->Void) {
//refresh.beginRefreshing()
let urlPath = urlInput
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
println("started second request")
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
if error != nil {
println(error)
let stringError = error.localizedDescription
CommonFunctions.showAlert("Ошибка", alertText: stringError, alertButtonText: "Закрыть", viewController: viewContr)
}
else{
var err: NSError?
if data != nil{
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSArray {
println("the number of news in json :\(jsonResult.count)")
if (err == nil) {
dispatch_async(dispatch_get_main_queue(), {
action(jsonResult)
hideLoadingViewAndStopAnimating()
tableName.reloadData()
refresh.endRefreshing()
})
}else{
let stringError = err?.localizedDescription
hideLoadingViewAndStopAnimating()
CommonFunctions.showAlert("Ошибка", alertText: stringError!, alertButtonText: "Закрыть", viewController: viewContr)
}
}else {
hideLoadingViewAndStopAnimating()
println("json is not valid")
var dic = dictForErrors()
CommonFunctions.showAlert("Ошибка", alertText: dic.alertText, alertButtonText: "Закрыть", viewController: viewContr)
}
}else{
CommonFunctions.showAlert("Ошибка", alertText: "data is nil",alertButtonText: "Закрыть", viewController: viewContr)
println("json data is nil")
}
}
})
task.resume()
}
This is how I am making request in order to download two request
self.refreshControl = self.refreshController
self.refreshControl?.addTarget(self, action: "loadDataNewsLenta", forControlEvents: .ValueChanged)
if arrayMainPage.count > 0 {
activityView.alpha = 0.0
arrayNewSLenta = arrayMainPage
self.tableView.reloadData()
}else{
// loading first time news
activityView.alpha = 0.5
activityIndicator.startAnimating()
isFirstReq = true
arrayNewSLenta.removeAll(keepCapacity: false)
CommonFunctions.JSONRequest2(urlString, tableName: tableView, action: desirializeJSONToArray, refresh: self.refreshController, viewContr: self, hidLoadingViewAndStopAnimating)
}
I put the code above on viewdidload. This code is where I am reloading :
func hidLoadingViewAndStopAnimating() {
activityView.alpha = 0
activityIndicator.stopAnimating()
}
func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 110.0
}
func loadDataNewsLenta() {
arrayNewSLenta.removeAll(keepCapacity: false)
isFirstReq = true
refreshController.beginRefreshing()
CommonFunctions.JSONRequest2(urlString, tableName: tableView, action: desirializeJSONToArray, refresh: self.refreshController, viewContr: self, hidLoadingViewAndStopAnimating)
}
This is all about tableview code and desirialization of json to array:
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
println("it is news lenta count \(arrayNewSLenta.count)")
return arrayNewSLenta.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("lentaCell", forIndexPath: indexPath) as LentaTableViewCell
let inex = indexPath.row
cell.labelDateOfPublication.text = arrayNewSLenta[indexPath.row].pubDate
cell.labeltitle.text = arrayNewSLenta[indexPath.row].pageTitle
return cell
}
func desirializeJSONToArray(jsonArray: NSArray){
for singleJSON in jsonArray{
var singleArticle = ArticleInfo()
if let arrayText = singleJSON as? NSDictionary{
if let articleID = arrayText["id"] as? NSString{
singleArticle.articleID = articleID
}
if let pageTitle = arrayText["pagetitle"] as? NSString {
singleArticle.pageTitle = pageTitle
}
if let longTitle = arrayText["longtitle"] as? String{
singleArticle.longTitle = longTitle
}
if let introText = arrayText["introtext"] as? String{
singleArticle.introText = introText
}
if let contentText = arrayText["content_text"] as? String{
singleArticle.contentText = contentText
}
if let category = arrayText["category"] as? String{
singleArticle.category = category
}
if let imageLink = arrayText["thumbnail"] as? String{
singleArticle.linkToImage = imageLink
}
if let videoLink = arrayText["video"] as? String{
singleArticle.videoLink = videoLink
println("hre is video link")
println(videoLink)
}
if let sity = arrayText["sity"] as? String{
singleArticle.sity = sity
}
if let visible = arrayText["visible"] as? String{
singleArticle.visible = visible
}
if let visits = arrayText["visits"] as? String{
singleArticle.visits = visits
}
if let pubDate = arrayText["pubdate"] as? String{
singleArticle.pubDate = pubDate
}
// insert result into array
arrayNewSLenta.append(singleArticle)
}
}
//cycle ended
if isFirstReq == true {
let urlStringSecondRequest = "http://www.kfdz/artifdcles/JsonMainList"
CommonFunctions.JSONRequest(urlStringSecondRequest, tableName: tableView, action: desirializeJSONToArray, refresh: self.refreshController, category: 0, viewContr: self, hidLoadingViewAndStopAnimating)
}
isFirstReq = false
}
The interesting fact I noticed , when I go to first vc and pull table immediately, for some reason it load cellAtIndex function using only the last row on screen(in my case index 4). on the other hand, when I go to first vc and choose some item (to see detailed view) and go back and pull table everything works.
On more issue to say, when I change transition style of uipageviewcontroller to PageCurl I dont have this problem!
You can instead of using 3 table view in a single view, you should use the container view in the first view and put all the view tables in different views as shown in the picture below :
You need 1 file for each table view created.
the bug can probably disappear like that

Resources