Cell in UITableView doesn't update until selected in iOS (swift) - ios

I'm developing an app with Parse that has a tableview with cells containing a label that has mutual friends from Facebook, my problem is that everything thing in the table works fine but the mutual friends label, it takes long to show unless I select the row (when I select the row the number appears immediately).. Here's my code for getting the mutual friends and setting the label in the cell:
let facebookContext = driverobj?.objectForKey("facebookContext") as! String
let user: PFUser = PFUser.currentUser()!
let access_token = user.valueForKey("authData")?.valueForKey("facebook")?.valueForKey("access_token") as! String
let usercontexturl: NSURL = NSURL(string: "https://graph.facebook.com/\(facebookContext)/mutual_friends?access_token=\(access_token)")!
let myrequest: NSURLRequest = NSURLRequest(URL: usercontexturl)
let mysession = NSURLSession.sharedSession()
let task = mysession.dataTaskWithRequest(myrequest) { data, response, error in
print("Task completed")
do {
let jsonresult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
cell.mutualFriendsLabel?.text = (jsonresult.valueForKey("summary")?.valueForKey("total_count"))!.stringValue + " Mutual Friends"
} catch {
print(error)
}
}
task.resume()
This code is inside cellForRowAtIndexPath method, and didSelectRowAtIndexPath method in empty.

The block passed to dataTaskWithRequest is probably not executing on the main thread which can cause these types of symptoms to appear. Try executing the UI updates to the main thread like:
dispatch_async(dispatch_get_main_queue()) {
cell.mutualFriendsLabel?.text = (jsonresult.valueForKey("summary")?.valueForKey("total_count"))!.stringValue + " Mutual Friends"
}

I think you need to update UI on main queue like this:
dispatch_async_(your_queue) {
task.response {
//handle data
dispatch_async(dispatch_get_main_queue()) {
//update UI, etc. change label text
}
}
}

Related

Passing Json object based on user selection in a tableview swift

ScreenshotHi all I'm working on a project where when the user selects a particular cell in a table view, it should show them the data( which is a JSON Decoded object), Now I did all the networking stuff but not sure how to pass the values in such a way that when the user selects a particular cell, corresponding values should appear.
This is the contents of my tableview
var items = [Canada,Delhi,Mumbai,London]
When the user selects pen then the first value(kindly see my screenshot) should be displayed, Also I don't want to store these values as a hardcoded value, I want it to update whenever the user taps on that cell, I've added my networking struct, I'm new here so forgive me if there is any error in my question.
Thank You!
struct StateManager {
func geturl(){
let url = "https://api.covidindiatracker.com/state_data.json"
networking(stateUrl: url)
}
func networking(stateUrl : String){
if let url = URL(string: stateUrl){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil{
print("Error fetching Data")
return
}else{
//This is the decoded Data
if let safeData = data{
do{
let json = try JSON(data: safeData)
for i in 0..<38{
if let total = json[i]["confirmed"].int{
print(total)
}
}
}catch{
print("Error ")
}
}
}
}
task.resume()
}
}
}

Images loading in incorrectly even with cache

if let toID = message.chatPartnerId() {
firebaseReference.child(toID).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
cell.nameLabel.text = dictionary["displayname"] as? String
let pic = dictionary["pictureURL"] as! String
print("THIS IS THE URL FOR EACH DISPLAYNAME")
print(dictionary["displayname"] as? String)
print(pic)
if let imageFromCache = MainPageVC.imageCache.object(forKey: pic as NSString) {
cell.pictureLabel.image = imageFromCache
} else {
let requested = URLRequest(url: URL(string: pic )!)
URLSession.shared.dataTask(with: requested) {data, response, err in
if err != nil {
print(err)
} else {
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
MainPageVC.imageCache.setObject(imageToCache!, forKey: pic as NSString)
//cell.pictureLabel.image = nil
cell.pictureLabel.image = imageToCache
}
}
}.resume()
}
}
})
}
return cell
}
I'm running this code in my cellForRowAtIndexPath and I'm getting a ton of really bad behavior. I'm also getting similar behavior on other pages but for some reason this block of code with about a 90% consistency returns incorrect information for cells.
I get a lot of duplicate pictures being used, displaynames in the wrong places, but when I'm actually clicking into a person, my detail page shows the correct information every single time. That code is the typical didSelectRowAtIndexPath and passing the person.
What I don't understand is why on the initial load of this page all of the information is screwed up, but if I click into someone and come back the entire tableview has correct names and pictures. The names/pics also fix if I scroll a cell off the screen then come back to it.
I'm getting this behavior all over my app, meanwhile I see caching/loading done like this everywhere. Is it because I'm running the code in my cellForRowAtIndexPath? The only difference I see is that I'm running it there instead of creating a function inside of my Person class that configures cells and running it like that. What I don't understand is why that would make a difference because as far as I'm aware running a function within cellforRowAtIndexpath would be the same as copy-pasting that same code into there?
Any ideas/suggestions?
Edit: I'm getting a very similar situation when I'm running the following code:
self.PersonalSearchesList = self.PersonalSearchesList.sorted{ $0.users > $1.users }
self.tableView.reloadData()
Where I'm sorting my array before reloading my data. The information sometimes loads in incorrectly at first, but once I scroll the cell off the screen then come back to it it always corrects itself.
if you are using swift 3 here are some handy functions that allow you to save an image to your apps directory from an URL and then access it from anywhere in the app:
func saveCurrentUserImage(toDirectory urlString:String?) {
if urlString != nil {
let imgURL: URL = URL(string: urlString!)!
let request: URLRequest = URLRequest(url: imgURL)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
let userImage = UIImage(data: data!)
if let userImageData = UIImagePNGRepresentation(userImage!) {
let filename = self.getDocumentsDirectory().appendingPathComponent("userImage")
try? userImageData.write(to: URL(fileURLWithPath: filename), options: [.atomic])
}
}
DispatchQueue.main.async(execute: display_image)
}
})
task.resume()
}
}
and then access it with any view controller using this:
extension UIViewController {
func getImage(withName name: String) -> UIImage {
let readPath = getDocumentsDirectory().appendingPathComponent(name)
let image = UIImage(contentsOfFile: readPath)
return image!
}
}
and finally calling it like this:
cell.pictureLabel.image = getImage(withName: "userImage")
If you can run the saveCurrentUserImage function prior to running cellForRowAtIndexPath then you can just check if the photo is nil in the directory before attempting to download it. You might be getting funny behavior when the page initially loads because you have multiple network calls going on at once. I wouldn't recommend making any network calls in cellForRowAtIndexPath because every time the cells are re-initialized it's going to make that network call for each cell.
Hope it helps!
EDIT: This method of image saving and retrieval is for images that you want to persist. If you want to erase them from memory you'll have to delete them from your directory.

How do I get a value from an NSURLSession task into an instance variable?

I have a tableView which I want to fill with a list of items provided by a web service. The service returns a JSON object with status (success or failure) and shows (an array of strings).
In viewDidLoad I call the custom method getShowsFromService()
func getShowsFromService() {
// Send user data to server side
let myURL = NSURL(string: "https://myurl.com/srvc/shows.php")
// Create session instance
let session = NSURLSession.sharedSession()
var json:NSDictionary = [:]
// Create the task
let task = session.dataTaskWithURL(myURL!) { //.dataTaskWithRequest(request) {
(data, response, error) in
guard let data = data else {
print("Error: \(error!.code)")
print("\(error!.localizedDescription)")
return
}
do {
json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! NSDictionary
} catch {
print (error)
}
let sts = json["status"] as! NSString
print("\(sts)")
}
// Resume the task so it starts
task.resume()
let shows = json["shows"] as! NSArray
for show in shows {
let thisshow = show as! String
showsArray.append(thisshow)
}
// Here I get "fatal error: unexpectedly found nil while unwrapping an Optional value"
}
The method receives the JSON object and puts it into a dictionary. Then I want to use that dictionary to call json['shows'] in order to get to the array of shows which I want to store in an instance variable called showsArray. The idea is to use showsArray in tableView(cellForRowAtIndexPath) in order to fill in the data.
The problem is that I can't get the Dictionary into the variable. If I try to do it inside the task, I get an error that says I need to call self.showsArray and if I do, the data doesn't go inside the array. If I do it outside the task I get an error because it says I'm trying to force unwrap a nil value.
How can I get the Dictionary created within the task out into the showsArray var?
The dataTaskWithURL method makes an async call, so as soon as you do task.resume() it will jump to the next line, and json["shows"] will return nil as the dictionary is empty at this point.
I would recommend moving that logic to a completion handler somewhere in your class. Something along the lines of:
func getShowsFromService() {
let myURL = NSURL(string: "https://myurl.com/srvc/shows.php")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(myURL!, completionHandler: handleResult)
task.resume()
}
//-handle your result
func handleResult(data: NSData?, response: NSURLResponse?, error: NSError?) {
guard let data = data else {
print("Error: \(error!.code)")
print("\(error!.localizedDescription)")
return
}
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! NSDictionary {
if let shows = json["shows"] as! NSArray {
//- this is still in a separate thread
//- lets go back to the main thread!
dispatch_async(dispatch_get_main_queue(), {
//- this happens in the main thread
for show in shows {
showsArray.append(show as! String)
}
//- When we've got our data ready, reload the table
self.MyTableView.reloadData()
self.refreshControl?.endRefreshing()
});
}
}
} catch {
print (error)
}
}
The snippet above should serve as a guide (I dont have access to a playground atm).
Note the following:
as soon as the task completes (asynchronously -> different thread) it will call the new function handleResult which will check for errors and if not, it will use the dispatcher to perform your task on the main thread. I'm assuming showsArrays is a class property.
I hope this helps
EDIT:
As soon as you fetch your data you need to reload the table (updated code above). You can use a refresh control (declare it as a class property).
var refreshControl: UIRefreshControl!
Then when you finish getting your data you can refresh:
self.MyTableView.reloadData()
self.refreshControl?.endRefreshing()
This will call your delegate methods to populate the rows and sections.

Download JSON Data THEN Reload Collection View

Quick one here. I have a class called NetworkService. It has a method that sets up an NSURLSession and completion block. My Object class, NewsItem, has a method downloadNewsItems that calls the first method and goes to a url, downloads JSON data, appends it to an array and returns it. up until that point everything works as i'd want. I create the object and append it then return it (the method is named downloadImage but it can work with any sort of data).
func downloadImage(completion: (NSData -> Void)) {
let request = NSURLRequest(URL: self.url)
let dataTask = session.dataTaskWithRequest(request) { (data, response, error) in
if error == nil {
if let httpResponse = response as? NSHTTPURLResponse {
switch (httpResponse.statusCode) {
case 200:
if let data = data {
completion(data)
}
default:
print(httpResponse.statusCode)
}
}
} else {
print("Error: \(error?.localizedDescription)")
}
}
dataTask.resume()
}
here's the implementation of the same method on my object class.
static func downloadNewsItems() -> [NewsItem] {
var newsItems = [NewsItem]()
let url = NSURL(string: "http://my-url.com.json")
let networkService = NetworkService(url: url!)
networkService.downloadImage { (data) -> Void in
let jsonData = JSON(data:data)
for item in jsonData["stories"].arrayValue {
// print(item["title"])
let newsArticle = NewsItem()
newsArticle.category = item["category"].string
newsArticle.titleText = item["title"].string
newsArticle.paragraph1 = item["paragraph1"].string
newsArticle.paragraph2 = item["paragraph2"].string
newsArticle.featureImage = NSURL(string: "\(item["headerImage"].string)")
newsArticle.date = item["date"].string
newsArticle.majorReference = item["majorReference"].string
newsArticle.fact = item["fact"].string
newsItems.append(newsArticle)
}
print(newsItems.count)
}
return newsItems
}
That print(newsItems.count) shows I have downloaded and updated my objects properly into a dictionary. Now here comes the problem. I have a CollectionViewController. I want to populate it with the data I get from the method call. I create an array and call the method that returns the objects on it inside of ViewDidLoad: but NO! when I print, I get 0 and my collectionView doesn't display any cells.
var newsItems = [NewsItem]()
then in viewDidLoad:
newsItems = NewsItem.downloadNewsItems()
print(newsItems.count)
collectionView.reloadData()
The objects are downloaded, get set up by my init() method and are added to the array in the method whilst inside of the NetworkService / NewsItem classes but when I call the method from the Collection View Controller, Nothing. Initially I tried the default JSONSerialisation route but i had the same problem. I thought maybe I'm not doing it right. Switched to a 3rd party JSON Library (SwiftyJSON)... Exact SAME PROBLEM. Please help. I have had 3 weeks of this. I.. I can't. Not anymore..
user2361090,
You are making a webservice call to fetch all the newsItems inside downloadNewsItems. Which is an asynchronous call. So it takes a little bit of time to fetch process and then populate the newsItems array. But you are not waiting for it to get populated even before it gets populated you have returned it Hence you will return empty array in newsItems = NewsItem.downloadNewsItems(). Use blocks to handover the data. Change your method as
static func downloadNewsItems(completionBlock block : ([NewsItem]) -> ()){
var newsItems = [NewsItem]()
let url = NSURL(string: "http://my-url.com.json")
let networkService = NetworkService(url: url!)
networkService.downloadImage { (data) -> Void in
let jsonData = JSON(data:data)
for item in jsonData["stories"].arrayValue {
// print(item["title"])
let newsArticle = NewsItem()
newsArticle.category = item["category"].string
newsArticle.titleText = item["title"].string
newsArticle.paragraph1 = item["paragraph1"].string
newsArticle.paragraph2 = item["paragraph2"].string
newsArticle.featureImage = NSURL(string: "\(item["headerImage"].string)")
newsArticle.date = item["date"].string
newsArticle.majorReference = item["majorReference"].string
newsArticle.fact = item["fact"].string
newsItems.append(newsArticle)
}
print(newsItems.count)
//if the control comes here in background thread because you know you have to reload the collection view which is UI operation change it main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
block(newsItems)
})
}
}
Finally you can call it as,
NewsItem.downloadNewsItems{ (passedArray) -> () in
newsItems = passedArray
collectionView.reloadData()
}

Apple Watch-2 page-based navigation display HTTP data on page changed

I try to create an page-based navigation watch app with Swift 2.
My app is below:
For both interfaces I have unique controllers named InterfaceController and EconomyInterfaceController.
In each controller I read some JSON data with function in controller init() function
func setFeed(url: String) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSURLRequest(URL: NSURL(string: url)!)
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if let data = data {
do {
let _data: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
if let items = _data!["data"] as? NSArray{
self.tableOutlet.setNumberOfRows(items.count, withRowType: "row")
for (index, item) in items.enumerate() {
if let title = self.JSONString(item["title"]) {
if let spot = self.JSONString((item["spot"])) {
let news = newsItem(title: title, spot: spot)
let row = self.tableOutlet!.rowControllerAtIndex(index) as? RowController
row!.rowLabel!.setText(news.title) }
}
}
}
} catch {
}
}
}
task.resume()
}
As I know this is not best way for HTTP request because of all request run on main thread and on app startup. This is not my main question but if you have any offer I cannot refuse :)
My main question is if I call setFeed() method in willActivate() method and set table labels with
row!.rowLabel!.setText(news.title)
my app works but this is not a good choice because on each time page changed this updates content again and again.
If I call setFeed() method in init() method and set table labels with
row!.rowLabel!.setText(news.title)
Only app's first page being displayed and other pages not being displayed. What is wrong here?

Resources