Swift 2.0 iOS9 UITableView update does not render - ios

When i load a JSON file inside my UITableViewController it loads and updates my datasource and view, but only renders the update when i touch my screen.
The loading and parsing code i'm using looks like this:
func fetchData() {
let jsonUrl = 'http://myrestserver.com/apicall'
let session = NSURLSession.sharedSession()
let urlObject = NSURL(string: jsonUrl)
let task = session.dataTaskWithURL(urlObject!) {
(data, response, error) -> Void in
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! NSDictionary
var items :[Article] = []
let jsonItems = jsonData["channel"] as! NSArray
for (_, item) in jsonItems.enumerate() {
let article = Article()
article.id = item["id"] as? String
article.title = item["title"] as? String
article.guid = item["guid"] as? String
items.append(article)
}
self.articles.insertContentsOf(items, at: 0)
} catch {
print("error fetchData")
}
}
task.resume()
self.tableView.reloadData()
}
Is there a method i'm not aware of to handle this re-rendering?
I've tried render methods for UITableViewCell like described here:
setNeedsLayout and setNeedsDisplay
But there is no luck, can someone explain what is the best practice for rendering new records?
Best regards,
Jos

#nwales is correct, though I would recommend getting familiar with property observers for reloading your data. Once your data is reloaded simply update your property and it will automatically fire your update.
var data: [String] = [""] {
didSet {
// you could call a function or just reload right here
self.tableView.reloadData()
}
}
using #nwales method:
var data: [String] = [""] {
didSet {
dispatch_async(dispatch_get_main_queue(),{
myTableView.reloadData()
})
}
}

After you've parsed the JSON try adding the following
dispatch_async(dispatch_get_main_queue(),{
myTableView.reloadData() //myTableView = your table view instance
})

Related

How to show all data in table view during pagination in swift 3?

Here i had implemented pagination for the table view and items are loaded by using model class but here the loaded items are replacing with the new items and whenever it calls api it returns the new data and old data is overriding on it and displaying only 10 items at a time i am implementing it for first time can anyone help me how to resolve the issue ?
func listCategoryDownloadJsonWithURL(listUrl: String) {
let url = URL(string: listUrl)!
print(listUrl)
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil { print(error!); return }
do {
if let jsonObj = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
self.listClassModel = ModelClass(dict: jsonObj as [String : AnyObject])
DispatchQueue.main.async {
guard let obj = self.listClassModel else { return }
let itemsCount = obj.items.count
print(itemsCount)
for i in 0..<itemsCount {
let customAttribute = obj.items[i].customAttribute
for j in 0..<customAttribute.count {
if customAttribute[j].attributeCode == "image" {
let baseUrl = "http://192.168.1.11/magento2/pub/media/catalog/product"
self.listCategoryImageArray.append(baseUrl + customAttribute[j].value)
print(self.listCategoryImageArray)
}
}
}
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.reloadData()
self.collectionView.isHidden = false
self.tableView.reloadData()
}
}
} catch {
print(error)
}
}
task.resume()
}
You are assigning your result data to model array, each time you call your API. This is the reason that your old data is getting replaced with new one. Rather than assigning, you should append the new data to your datasource array.
if let jsonObj = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
self.listClassModel.append(contentsOf: ModelClass(dict: jsonObj as [String : AnyObject]))
Also make sure you initialize your array as an empty array first. (maybe in declaration or viewDidLoad) before calling API.

Swift 3/iOS UIView not updating after retrieving remote JSON data

I have a UITableView with a list of users. When you tap on a row, the uid of the user is passed to the UIViewController detail view. A URLRequest is made to retrieve JSON data of the user (username, avatar, etc). However, the detail view inconsistently updates the information. Sometimes it'll show the users' name, avatar, etc but other times it'll show nothing or it'll only show the username or only show the avatar, etc.
In the fetchUser() method, I have a print("Username: \(self.user.username)") that shows the correct data is being retrieved 100% of the time but it won't display it 100% of the time in the view.
Any help would be greatly appreciated.
Thanks!
class ProfileViewController: UIViewController {
#IBOutlet weak var avatarImageView: UIImageView!
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var networthLabel: UILabel!
var user: User!
var uid: Int?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
fetchUser()
}
func reloadView() {
self.usernameLabel.text = user.username
self.networthLabel.text = "$" + NumberFormatter.localizedString(from: Int((user.networth)!)! as NSNumber, number: NumberFormatter.Style.decimal)
self.avatarImageView.downloadImage(from: user.avatar!)
circularImage(photoImageView: self.avatarImageView)
}
func fetchUser() {
// Post user data to server
let myUrl = NSURL(string: "http://localhost/test/profile")
let urlRequest = NSMutableURLRequest(url: myUrl! as URL);
urlRequest.httpMethod = "POST"
let postString = "uid=\(uid!)"
urlRequest.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: urlRequest as URLRequest) { (data, response, error) in
if (error != nil) {
print("error=\(String(describing: error))")
return
} // end if
self.user = User()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String : AnyObject]
if let parseJSON = json?["data"] as? [[String : AnyObject]] {
for userFromJson in parseJSON {
let userData = User()
if let uid = userFromJson["uid"] as? String,
let username = userFromJson["username"] as? String,
let networth = userFromJson["networth"] as? String,
let avatar = userFromJson["avatar"] as? String {
userData.uid = Int(uid)
userData.username = username
userData.networth = networth
userData.avatar = avatar
self.usernameLabel.text = username
self.networthLabel.text = networth
self.avatarImageView.downloadImage(from: avatar)
circularImage(photoImageView: self.avatarImageView)
} // end if
self.user = userData
} // end for
} // end if
DispatchQueue.main.async {
print("Username: \(self.user.username)")
self.reloadView()
}
} catch let error {
print(error)
}
}
task.resume()
}
Firstly, call fetch user in viewWillAppear like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchUser()
}
Then, change the code here like I did, don't use the reloadView function you had, instead, update the UI elements on the main thread at the end of the fetchUser function. I also changed it so you weren't updating the UI twice because you have 4 lines at the bottom of the if let uid = ... statement in fetchUser which updated UI elements that wasn't in the main thread which is why in my version I removed those 4 lines of code. Let me know if this worked for you.
let task = URLSession.shared.dataTask(with: urlRequest as URLRequest) { (data, response, error) in
if (error != nil) {
print("error=\(String(describing: error))")
return
} // end if
self.user = User()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String : AnyObject]
if let parseJSON = json?["data"] as? [[String : AnyObject]] {
for userFromJson in parseJSON {
let userData = User()
if let uid = userFromJson["uid"] as? String,
let username = userFromJson["username"] as? String,
let networth = userFromJson["networth"] as? String,
let avatar = userFromJson["avatar"] as? String {
userData.uid = Int(uid)
userData.username = username
userData.networth = networth
userData.avatar = avatar
} // end if
self.user = userData
} // end for
} // end if
DispatchQueue.main.async {
self.usernameLabel.text = user.username
self.networthLabel.text = "$" + NumberFormatter.localizedString(from: Int((user.networth)!)! as NSNumber, number: NumberFormatter.Style.decimal)
self.avatarImageView.downloadImage(from: user.avatar!)
circularImage(photoImageView: self.avatarImageView)
}
} catch let error {
print(error)
}
}
task.resume()
Two suggestions:
strictly speaking, all accesses to UIView object should be on the main thread. You're dispatching to the main thread to call reloadView, but should probably also do it when you're settings the "username" and "net worth" values on the labels
are you sure that the labels are blank? Could it be an autolayout problem instead? (Try setting the background colour of the labels to yellow, to check that they're the correct size. Sometimes autolayout can squash views down to nothing if there are conflicting constraints)

Getting data not according to the same order that I call those methods in swift

I have aded HMSegmentedControl to make a swiping segmented control in my iOS app.I am loading all the data initially because then it will facilitate the scrolling. So I have to load several tables under several categories. Category name is the segmented control item title. So this is how I set my titles.
for(var i=0; i<dm.TableData.count; i++)
{
self.array.append(dm.TableData[i]["name"] as! String)
}
segmentedControl.sectionTitles=self.array
Categories are loading according to the order of this array without any issue. Then I am loading my tables like this.
for i in 0..<dm.TableData.count {
self.catID=self.dm.TableData[i]["term_id"] as? String
switch self.catID {
case "55":
self.jsonParser()
case "1":
self.getBusiness()
case "2":
self.getNews()
case "4":
self.getSports()
case "5":
self.getEntertainment()
case "57":
self.getCrime()
case "21":
self.getPolitics()
case "28":
self.getWorld()
case "89":
self.getVideos()
case "111":
self.getLocalNews()
default:
print("Default")
}
}
This is my jsonParser method. getBusiness(),getNews(),getSports() all those methods are just same as this and load to seperate array and the dictionary key is different.
func jsonParser() {
let urlPath = "http://www.li67t8.lk/mobileapp/news.php?"
let category_id=self.catID
let catParam="category_id"
let strCatID="\(catParam)=\(category_id)"
let strStartRec:String=String(startRec)
let startRecPAram="start_record_index"
let strStartRecFull="\(startRecPAram)=\(strStartRec)"
let strNumOfRecFull="no_of_records=10"
let fullURL = "\(urlPath)\(strCatID)&\(strStartRecFull)&\(strNumOfRecFull)"
print(fullURL)
guard let endpoint = NSURL(string: fullURL) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(URL:endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
print(json)
if let countries_list = json["data"] as? NSArray
{
// for (var i = 0; i < countries_list.count ; i++ )
for i in 0..<countries_list.count
{
if let country_obj = countries_list[i] as? NSDictionary
{
//self.TableData.append(country_obj)
self.breakingNews.append(country_obj)
}
}
dispatch_async(dispatch_get_main_queue()) {
print("%%%%%%%%%%% CAT ID %%%%%%%%%% \(self.catID)")
if let checkedUrl = NSURL(string: self.breakingNews[0]["thumb_url"] as! String) {
self.imageURL=checkedUrl
}
if let time = self.breakingNews[0]["duration"] as? String
{
self.timeDuration=time
}
if let likes = self.breakingNews[0]["read_count"] as? String
{
self.noOfLikes=likes
}
if let title = self.breakingNews[0]["post_title"] as? String
{
self.titleNews=title
}
self.addedArray.append("Breaking News")
self.commonData["Breaking News"]=self.breakingNews
self.updateUI()
print("-------BREAKING--------")
}
}
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
I have one method for UpdateUI() and it creates UITableView dynamically and assign tag value dynamically (I keep an Int called index and I assign that index to tableview tag and after adding table to super view I increment the index count by 1)
According to this I get data and load to the tableview. But my problem is data not getting in the same order I call to those methods. As an example jsonParser() returns its data set and then it returns getSportsData() data. like wise my data not according to the segment title order.
So how can I solve this problem? Please help me.
Thanks

Swift: Trying to retrieve the data from a dataSource Struct

Here is my code below:
class DataSource: NSObject {
var categories = [String]()
var items = [Item]()
private override init() {
super.init()
}
class var sharedDataSource: DataSource {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: DataSource!
}
dispatch_once(&Static.onceToken) {
let dataSource = DataSource()
Static.instance = dataSource
let urlPath = "myUrlPathString"
let endpoint = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL: endpoint!)
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: { (data, response, error) in
let json = JSON(data: data!)
print(json)
for obj in json.arrayValue {
let item: Item = Item()
item.itemID = obj["item"].stringValue
item.price = obj["price"].floatValue
item.title = obj["title"].stringValue
item.category = obj["category"].stringValue
item.available = obj["available"].boolValue
item.image = obj["image"].stringValue
print(item.title)
dataSource.items.append(item)
print(dataSource.items)
print("STOP")
}
}).resume()
}
return Static.instance
}
}
I am trying to use the result of this dataSource in a UICollectionView, by assigning its result to an Item array. I am successfully grabbing the data in my NSURLSession, and its local list, 'items', is being populated.
In my UICollectionView, in my viewDidLoad, I am assigning my local variable as follows:
let dataSource = DataSource()
items = dataSource.sharedInstance.items
Printing the value within the viewDidLoad always results in an empty array with no values, but I know the values are there by the time NURLSession is finished. I'm not sure how to write a completionhandler for this. This is my first time doing this kind of thing with a sharedDataSource that is a struct.
Any ideas anyone?
Thanks,
Sean
If you are doing this in viewDidLoad then it's likely the NSURL session has not completed by the time you try to get your items array. Send a notification in your dataSource class after the data has been received and then set items.
Eg
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: { (data, response, error) in
let json = JSON(data: data!)
print(json)
for obj in json.arrayValue {
let item: Item = Item()
item.itemID = obj["item"].stringValue
item.price = obj["price"].floatValue
item.title = obj["title"].stringValue
item.category = obj["category"].stringValue
item.available = obj["available"].boolValue
item.image = obj["image"].stringValue
print(item.title)
dataSource.items.append(item)
print(dataSource.items)
print("STOP")
}
NSNotificationCenter.defaultCenter().postNotificationName("dataRecievedNotification", object: nil)
}).resume()
But you also need to be able to receive this notification in your ViewController with the collectionView. So in viewDidLoad add
NSNotificationCenter.defaultCenter().addObserver(self, selector: "dataRecieved", name: "dataRecievedNotification", object: nil)
Then add a function in the same ViewController:
func dataRecieved() {
print("data received")
items = dataSource.sharedInstance.items
collectionView.reloadData()
}
Where dataSource is a variable declared above viewDidLoad:
let dataSource = DataSource()
Don't forget that if you are using observers you need to remove them when the class is removed from memory, so in the ViewController add this deinit function:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
EDIT: I think your singleton pattern can be simplified using modern swift. You don't have to use a struct or dispatch_once any more, just declaring a static let shared instance will handle all that for you. with that in mind, I tried to simplify your dataSource class:
class DataSource {
static let sharedInstance = DataSource()
var items = [Item]()
private init() {}
func retrieveItems() {
let urlPath = "myUrlPathString"
let endpoint = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL: endpoint!)
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: { (data, response, error) in
let json = JSON(data: data!)
print(json)
for obj in json.arrayValue {
let item: Item = Item()
item.itemID = obj["item"].stringValue
item.price = obj["price"].floatValue
item.title = obj["title"].stringValue
item.category = obj["category"].stringValue
item.available = obj["available"].boolValue
item.image = obj["image"].stringValue
print(item.title)
items.append(item)
print(items)
print("STOP")
}
NSNotificationCenter.defaultCenter().postNotificationName("dataRecievedNotification", object: nil)
}).resume()
}
}
where you, in viewDidLoad, add the following logic:
if DataSource.sharedInstance.items.count == 0 {
DataSource.sharedInstance.retrieveItems()
}
then change dataRecieved to
func dataRecieved() {
print("data received")
items = DataSource.sharedInstance.items //Notice the capital
collectionView.reloadData()
}
and delete your declaration of var dataSource = DataSource() above viewDidLoad

iOS - Why reloadData tableView data on first application load?

I am working on a simple Flickr app that gets some data from their API and displays it on a tableview instance. Here's a piece of the code for the TableViewController subclass.
var photos = [FlickrPhotoModel]()
override func viewDidLoad() {
super.viewDidLoad()
getFlickrPhotos()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
private func getFlickrPhotos() {
DataProvider.fetchFlickrPhotos { (error: NSError?, data: [FlickrPhotoModel]?) in
//data is received
dispatch_async(dispatch_get_main_queue(), {
if error == nil {
self.photos = data!
self.tableView.reloadData()
}
})
}
}
The application does not seem to load the data if the { tableView.reloadData() } line is removed. Does anyone know why this would happen since I call getFlickrPhotos() within viewDidLoad(). I believe I am also dispatching from the background thread in the appropriate place. Please let me know what I am doing incorrectly.
EDIT -- Data Provider code
class func fetchFlickrPhotos(onCompletion: FlickrResponse) {
let url: NSURL = NSURL(string: "https://api.flickr.com/services/rest/?method=flickr.photos.getRecent&api_key=\(Keys.apikey)&per_page=25&format=json&nojsoncallback=1")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if error != nil {
print("Error occured trying to fetch photos")
onCompletion(error, nil)
return
}
do {
let jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
let photosContainer = jsonResults!["photos"] as? NSDictionary
let photoArray = photosContainer!["photo"] as? [NSDictionary]
let flickrPhoto: [FlickrPhotoModel] = photoArray!.map{
photo in
let id = photo["id"] as? String ?? ""
let farm = photo["farm"] as? Int ?? 0
let secret = photo["secret"] as? String ?? ""
let server = photo["server"] as? String ?? ""
var title = photo["title"] as? String ?? "No title available"
if title == "" {
title = "No title available"
}
let model = FlickrPhotoModel(id: id, farm: farm, server: server, secret: secret, title: title)
return model
}
//the request was successful and flickrPhoto contains the data
onCompletion(nil, flickrPhoto)
} catch let conversionError as NSError {
print("Error parsing json results")
onCompletion(conversionError, nil)
}
}
task.resume()
}
I'm not familiar with that API, but it looks like the fetchFlickrPhotos method is called asynchronously on a background thread. That means that the rest of the application will not wait for it to finish before moving on. viewDidLoad will call the method, but then move on without waiting for it to finish.
The completion handler that you provide is called after the photos are done downloading which, depending on the number and size of the photos, could be seconds later. So reloadData is necessary to refresh the table view after the photos are actually done downloading.

Resources