self.tableView.reloadData() not working in Swift
I'm attempting to learn Swift
& the basics of iOS
dev at the same time, so bear with me. I've got a TableViewController
that is firstly parsing a local JSON
file and rendering it's very simple data into TableViewCell
and SectionHeaderViews. Within the same TableViewController
, I'm making a call to a JSON
endpoint, which is returning data, which I am then setting to variables so I can access what I actually want to get at (the API structure is less than desirable). So, I finally set the proper data to be self.tableData
and then call self.tableView.reloadData()
but nothing happens. What gives?
import UIKit
class BusinessTableViewController: UITableViewController {
var data: NSMutableData = NSMutableData()
var tableData: NSArray = NSArray()
@lazy var Business: NSArray = {
let pathTCT = NSBundle.mainBundle().pathForResource("TCT", ofType: "json")
let data = NSData.dataWithContentsOfFile(pathTCT, options: nil, error: nil)
return NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as NSArray
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = UIImageView(image: UIImage(named: "growler"))
tableView.registerClass(BeerTableViewCell.self, forCellReuseIdentifier: "cell")
tableView.separatorStyle = .None
fetchKimono()
}
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
// return Business.count
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
let biz = Business[section] as NSDictionary
let results = biz["results"] as NSDictionary
let beers = results["collection1"] as NSArray
return beers.count
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as BeerTableViewCell
if let path = indexPath {
let biz = Business[path.section] as NSDictionary
let results = biz["results"] as NSDictionary
let beers = results["collection1"] as NSArray
let beer = beers[path.row] as NSDictionary
cell.titleLabel.text = beer["BeerName"] as String
}
return cell
}
override func tableView(tableView: UITableView!, titleForHeaderInSection section: Int) -> String! {
let biz = Business[section] as NSDictionary
return biz["name"] as String
}
override func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
let biz = Business[section] as NSDictionary
let view = LocationHeaderView()
view.titleLabel.text = (biz["name"] as String).uppercaseString
return view
}
override func tableView(tableView: UITableView!, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func fetchKimono() {
var urlPath = "names have been changed to protect the innocent"
var url: NSURL = NSURL(string: urlPath)
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
connection.start()
}
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
// Recieved a new request, clear out the data object
self.data = NSMutableData()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
// Append the recieved chunk of data to our data object
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
// Request complete, self.data should now hold the resulting info
// Convert the retrieved data in to an object through JSON deserialization
var err: NSError
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var results: NSDictionary = jsonResult["results"] as NSDictionary
var collection: NSArray = results["collection1"] as NSArray
if jsonResult.count>0 && collection.count>0 {
var results: NSArray = collection as NSArray
self.tableData = results
self.tableView.reloadData()
}
}
}
You'll need to reload the table on the UI
thread via:
//swift 2.3
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
//swift 5
DispatchQueue.main.async{
self.tableView.reloadData()
}
Follow up:
An easier alternative to the connection.start()
approach is to instead use NSURLConnection.sendAsynchronousRequest(...)
//NSOperationQueue.mainQueue() is the main thread
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: url), queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in
//check error
var jsonError: NSError?
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &jsonError)
//check jsonError
self.collectionView?.reloadData()
}
This doesn't allow you the flexibility of tracking the bytes though, for example you might want to calculate the progress of the download via bytesDownloaded/bytesNeeded
You have just to enter:
First a IBOutlet:
@IBOutlet var appsTableView : UITableView
Then in a Action func:
self.appsTableView.reloadData()
If your connection is in background thread then you should update UI in main thread like this
self.tblMainTable.performSelectorOnMainThread(Selector("reloadData"), withObject: nil, waitUntilDone: true)
As I have mentioned here
Swift 4:
self.tblMainTable.performSelector(onMainThread: #selector(UICollectionView.reloadData), with: nil, waitUntilDone: true)
In my case the table was updated correctly, but setNeedDisplay() was not called for the image so I mistakenly thought that the data was not reloaded.
So, the issue was that I was trying to inappropriately use @lazy, which caused my Business variable to essentially be a constant, and thusly uneditable. Also, instead of loading the local json, I'm now loading only the data returned from the API.
import UIKit
class BusinessTableViewController: UITableViewController {
var data: NSMutableData = NSMutableData()
var Business: NSMutableArray = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = UIImageView(image: UIImage(named: "growler"))
tableView.registerClass(BeerTableViewCell.self, forCellReuseIdentifier: "cell")
tableView.separatorStyle = .None
fetchKimono()
}
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
return Business.count
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
if (Business.count > 0) {
let biz = Business[section] as NSDictionary
let beers = biz["results"] as NSArray
return beers.count
} else {
return 0;
}
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as BeerTableViewCell
if let path = indexPath {
let biz = Business[path.section] as NSDictionary
let beers = biz["results"] as NSArray
let beer = beers[path.row] as NSDictionary
cell.titleLabel.text = beer["BeerName"] as String
} else {
cell.titleLabel.text = "Loading"
}
return cell
}
override func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
let view = LocationHeaderView()
let biz = Business[section] as NSDictionary
if (Business.count > 0) {
let count = "\(Business.count)"
view.titleLabel.text = (biz["name"] as String).uppercaseString
}
return view
}
override func tableView(tableView: UITableView!, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func fetchKimono() {
var urlPath = "names have been removed to protect the innocent"
var url: NSURL = NSURL(string: urlPath)
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
connection.start()
}
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
// Recieved a new request, clear out the data object
self.data = NSMutableData()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
// Append the recieved chunk of data to our data object
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
// Request complete, self.data should now hold the resulting info
// Convert the retrieved data in to an object through JSON deserialization
var err: NSError
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var results: NSDictionary = jsonResult["results"] as NSDictionary
var collection: NSArray = results["collection1"] as NSArray
if jsonResult.count>0 && collection.count>0 {
Business = jsonResult
tableView.reloadData()
}
}
}
Swift Docs on @lazy:
You must always declare a lazy property as a variable (with the var keyword), because its initial value may not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.