TableView Data Not Reloading Swift - swift

I am working on restaurant app where i need to get all restaurant type..i successfully get all data but tableview not reloading..
var arrSubMenu = [ResataurantType]()
//TableView Datasource And Delegate Method
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(arrSubMenu.count)
return arrSubMenu.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: leftMenuTableViewCell =
tableView.dequeueReusableCell(withIdentifier: "tableCell") as!
leftMenuTableViewCell
cell.name.text = self.arrSubMenu[indexPath.row].type
return cell
}
func getRestaurantType() {
let manager = AFHTTPSessionManager()
manager.requestSerializer = AFJSONRequestSerializer()
manager.get(RESTAURANTTYPE, parameters: nil, progress: nil, success: {
(operation, responseObj) in
if let objDic = responseObj as? [String:Any] {
if let objArray = objDic["RESTAURANT_TYPE"] as? NSArray {
for objType in objArray {
let ObjRestaurant = ResataurantType()
if let objString = objType as? String {
ObjRestaurant.type = objString
}
self.arrSubMenu.append(ObjRestaurant)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}) { (operation, error) in
print(error)
}
}
I call this function in ViewDidLoad() but still i can't polulate tableview with record

-These can be possible reason from my person experience
TableView's dataSource don't set to self.
-Verification :- Break point on tableView Data Source & Delegate methods.
your arrSubMenu array don't contain the single value.
-Verification:- Break point & print the arrSubMenu before reloading the tableView.

You have just to enter:
First a IBOutlet:
#IBOutlet var tableView : UITableView
In viewDidLoad:
tableView.dataSource = self //OR connection between tableView in storyboard and tableView in swift class

When are you calling getRestaurantType() ? Could it be that it is called before the tableview's datasource is assigned ? That could make the tableview appear empty although the underlying data is present. And, unless you call reloadData() at some other point in the program, it will not refresh itself.

Replace below at cell for row at index method
let cell =
tableView.dequeueReusableCell(withIdentifier: "tableCell" ,for: indexPath)as!
leftMenuTableViewCellenter

Related

unable to dequeue a cell with identifier cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

I am new to SWIFT and trying to show some data into table view controller but when I press the button it shows the above error. Please correct me
import UIKit
var selectedPlace : Place!
class ShowPlaceTableTableViewController: UITableViewController {
var places : [Place]!
override func viewDidLoad()
{
super.viewDidLoad()
places = readPlaces()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return places.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PlaceTableViewCell
cell.countryLabel.text = places[indexPath.row].country
cell.placeImageView.image = places[indexPath.row].picture
return cell
}
func readPlaces() -> [Place]
{
if UserDefaults.standard.object(forKey: "places") != nil
{
var data = UserDefaults.standard.object(forKey: "places") as! Data
let places = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [Place]
return places
}else
{
return [Place]()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedPlace = places[indexPath.row]
performSegue(withIdentifier: "detailSegue", sender: self)
}
}
Is your PlaceTableViewCell inside it's own xib separately OR is it inside the storyboard itself? You must register either a cell class or a nib before you try to dequeue the cell from tableView.
If it's inside the storyboard, then you can register the subclass to work with it.
tableView.register(PlaceTableViewCell.self, forCellReuseIdentifier: "PlaceTableViewCell")
If it's inside it's own xib separately, then you can register the UINib to work with it.
tableView.register(UINib(nibName: "PlaceTableViewCell", bundle: nil), forCellReuseIdentifier: "PlaceTableViewCell")
These need to be called once only, you can place these calls inside viewDidLoad().
Now you can dequeue it like following -
let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceTableViewCell") as! PlaceTableViewCell
Note : The reuseIdentifier must be same in following places -
storyboard (or xib)
at the time of registration tableView.register...
at the time of dequeue tableView.dequeue...

How to properly display pictures in a TableView from Firebase Storage in SWIFT

I have a chat app where people can talk in a group and a little picture is displayed in each cell to show who is talking. I managed to display these pictures from Firebase storage but it is not always the right picture which is displayed at the right place.
It only works when I go to the previous View Controller and coming back the chat View to see the pictures displayed properly in each cell.
I tried to use DispatchQueue.main.async {} probably not in the good way cause it did not work for me.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = messageArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "customMessageCell", for: indexPath) as! CustomMessageCell
cell.selectionStyle = .none
// CHANGE TEXT ACCORDING TO SENDER
if message.sender == Auth.auth().currentUser?.email{
cell.messageBubble.backgroundColor = UIColor(red:0.30, green:0.68, blue:1.5, alpha:1.0)
// ...
} else {
cell.messageBubble.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
// ...
}
let theTimeStamp = messageArray[indexPath.row].createdAt
let doubleTime = Double(theTimeStamp)
let myDate = Date(timeIntervalSince1970: doubleTime )
let dateToShow = myDate.calenderTimeSinceNow()
cell.messageBodyTextView.text = messageArray[indexPath.row].messageBody
cell.usernameLabel.text = messageArray[indexPath.row].name
cell.timeLabel.text = dateToShow
let imagePath = self.storageRef.reference(withPath:"\(message.uid)/resizes/profilImage_150x150.jpg")
imagePath.getData(maxSize: 10 * 1024 * 1024) { (data, error) in
if let error = error {
cell.userPicture.image = UIImage(named: "emptyProfilPic")
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
print("Got an error fetching data : \(error.localizedDescription)")
return
}
if let data = data {
cell.userPicture.image = UIImage(data: data)
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
}
}
return cell
}
Thank you for your help !
You have to prepare the cell to be reusable with the proper override prepareForReuse().
For more clean code I suggest to you to implement the cells in separate cocoa Touch classes so it's easier to override and prepare for next data incoming, avoiding your problem.
What I mean it's a sort of this:
class mineCell:UITableViewCell {
#IBOutlet weak var text:UILabel!
#IBOutlet weak var img:UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
func updateCell(dataIn){
.
.
}
override func prepareForReuse() {
text.text = ""
img.image = nil
}
In your cellForRowAt table implementation just call the update function and pass your data like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "mineCell"
if let cell = mineTable.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? mineCell {
updateCell(dataIn)
return cell
}
return mineCell()
}
In this way you are always sure that your cell will be ready for every reuse and not loading wrong data from the cell above.
Just to let you know, the problem was thaT I was reloading the table View after each message loaded. Instead, the best solution was to add a row to the tableview without reloaded the tableview after each message :
self.ConvertationTableView.insertRows(at: [IndexPath(row: self.messageArray.count - 1, section: 0)], with: .automatic)

My TableView initializes before JSON data is received. How do you prevent this?

I am coding an app that receives air pollution data from API. The code passes JSON from aqicn.com. The data is updated too slowly and the tableview is displayed first. So there isn't any data displayed for the data view. I have tried DispatchQueue.main.async but it did not work. Delays using DispatchQueue.main.asyncAfter did not work. Been stuck on this for several days now. Please give me suggestions or solutions!
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
let locationManager = CLLocationManager()
var pm25Data:Int?
var pm10Data:Int?
override func viewDidLoad() {
super.viewDidLoad()
performRequest(with: "https://api.waqi.info/feed/Tainan/?token=\(C.APIkey)")
tableView.dataSource = self
}
func performRequest(with typeURL: String)
{
if let url = URL(string: typeURL)
{
print("pass")
let session = URLSession.shared
print("pass")
let dataTask = session.dataTask(with: url) { (data, response, error) in
if error == nil && data != nil
{
let decoder = JSONDecoder()
do
{
let info = try decoder.decode(AirData.self, from: data!)
self.pm25Data = info.data.iaqi.pm25.v
self.pm10Data = info.data.iaqi.pm10.v
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch
{
print("we have an error")
}
}
}
dataTask.resume()
}
}
override func viewWillAppear(_ animated: Bool) {
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "cellId")
}
override open var shouldAutorotate:Bool
{
return false
}
func didFailWithError(error: Error) {
print(error)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell
if(indexPath.row == 0)
{
cell.descriptionName?
.text = "PM 2.5"
cell.valueNumber?.text = "\(pm25Data)"
}
else
{
cell.descriptionName?.text = "PM 10"
cell.valueNumber?.text = "\(pm10Data)"
}
return cell
}
}
One possible way...
Maintain an array of objects containing the data for each row, I will call it dataArray so that each object in the array would contain the information required to populate each respective row. Then, in your cellForRowAt function, after dequeueing the cell like you currently have implemented, do something like this:
if indexPath.row == 0 {
cell.descriptionName?.text = "PM 2.5"
cell.valueNumber?.text = "\(pm25Data)"
// Other setup unique to the first cell
} else {
cell.descriptionName?.text = dataArray[indexPath.item - 1].description
cell.valueNumber?.text = dataArray[indexPath.item - 1].description
// Other setup unique to all following cells
}
Also, implement your numberOfRowsInSection with one line, simply return dataArray.count
Then, once your dataArray is filled with objects containing the data, call self.tableView.reloadData()
In a broader but still simplified sense:
Every time you call reloadData(), a few things happen:
The tableView will call its numberOfRowsInSection function
The tableView will call its cellForRowAt function repeatedly the number of times returned from numberOfRowsInSection and set up each row individually
The tableView will call other functions like heightForRowAt as necessary to set up each row
Some things to keep in mind:
Call reloadData() every time you want to refresh the tableView (with new data, for example)
You don't really set up each cell individually, you set up the logic to populate cells with data pulled from an itemized source (usually an array)
All you then have to do is maintain the dataArray and then call reloadData() every time you want to refresh the tableView. The cellForRowAt function handles populating each cell with data.
A useful tutorial:
https://www.youtube.com/watch?reload=9&v=VFtsSEYDNRU

Send different nibs in a function swift 3

I have a tableView who need to contain two different view, the name of the first one is CustomTableViewCell the second one is CustomDeliveryTableViewCell
I want my variable to take the two cell, I don't understand this error.
Here my function
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell: UITableViewCell
cell = Bundle.main.loadNibNamed("CustomTableViewCell", owner: self, options: nil)?.first as! UITableViewCell
if (!self.appReady)
{
return cell
}
let arrayOfCard = self.selectedCard(section: indexPath.section, row: indexPath.row)
let json:JSON = JSON(arrayOfCard)
if (json[0]["cards"][indexPath.row]["category"] == "delivery")
{
cell = Bundle.main.loadNibNamed("CustomDeliveryTableViewCell", owner: self, options: nil)?.first as! CustomTableViewCell
}
cell = fillCell(cell: cell, json: json, index: indexPath.row)
return cell
}
My fonction fillCell is prototype like that
func fillCell(cell: CustomTableViewCell, json:JSON, index:Int) ->
CustomTableViewCell
Edit
Here the code of actual fillCell function
func fillCell(cell: UITableViewCell, json:JSON, index:Int) -> UITableViewCell {
if (json[0]["cards"][index]["category"] == "train")
{
if let type = json[0]["cards"][index]["category"] as JSON?
{
cell.labelType.text = type.string
}
if let departureStation = json[0]["cards"][index]["train"]["departure"]["station"] as JSON?
{
cell.labelDepartureStation.text = departureStation.string
}
// Do some code
}
else if (json[0]["cards"][index]["category"] == "delivery")
{
//Do some code
return cell
}
else{
//Do some code
return cell
}
}
Your initial assignment creates a cell of type CustomDeliveryTableViewCell.
Within the if block you're trying to assign a CustomTableViewCell to the same variable. This will only work if CustomTableViewCell is a subclass of CustomDeliveryTableViewCell
When you call fillCell( it's expecting CustomTableViewCell, but cell is a CustomDeliveryTableViewCell
If you declare var cell: UITableViewCell then you can assign either type to it.

Values Repeat When Scrolling in TableView

When I tap a button in a custom cell and then scroll down (or up) another cell button is also tapped. I see that it's tapped because the button outlet that I created for the button is disabled.
My cellForRowAtIndexPath has a reuseIdentifier for the cell:
var cell: FeedTableViewCell? = tableView.dequeueReusableCellWithIdentifier("MusicCell") as? FeedTableViewCell
Considering I have the degueueReusableCellWithId in the cellForRowAtIndexPath do I need a prepareForReuse? When I add the prepareForReuse in my custom cell file, the cell just goes back to the default values (obviously because I reset it to the default values). Problem is I want it to keep the value of each indexPath.row.
This is how I'm querying the values:
override func queryForTable() -> PFQuery {
var query:PFQuery = PFQuery(className:"Music")
if(objects?.count == 0)
{
query.cachePolicy = PFCachePolicy.CacheThenNetwork
}
query.orderByAscending("videoId")
return query
}
This is the numberOfRowsInSection and cellForRowAtIndexPath
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects!.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell: FeedTableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? FeedTableViewCell
if(cell == nil) {
cell = NSBundle.mainBundle().loadNibNamed("FeedTableViewCell", owner: self, options: nil)[0] as? FeedTableViewCell
}
if let pfObject = object {
//I took out the irrelevant methods. I can add them if that makes a difference...
var votes:Int? = pfObject["votes"] as? Int
if votes == nil {
votes = 0
}
cell?.votesLabel?.text = "\(votes!)"
}
I'm registering this in the viewDidLoad above the super.viewDidLoad()
tableView.registerNib(UINib(nibName: "FeedTableViewCell", bundle: nil), forCellReuseIdentifier: cellIdentifier)
This is my button query in the customCell:
#IBAction func heartButton(sender: AnyObject) {
if(parseObject != nil) {
if var votes:Int? = parseObject!.objectForKey("votes") as? Int {
votes!++
parseObject!.setObject(votes!, forKey: "votes")
parseObject!.saveInBackground()
votesLabel?.text = "\(votes!)"
}
}
heartOutlet.enabled = false
}
Any help and suggestions mean a lot.
Thank you.
REFRENCE LINKS I USED:
I referred to several links but they were in objective-c and didn't help:
UICollectionView Tap Selects More Than One Cell
How to use prepareForReuse method
I also referred to the docs, and that didn't help much.
From the code you have posted, it is clear that you are not setting the enabled property of the UIButton with respect to the DataSource(The array and its objects you are using to load the tableview, that is the elements in objects array). Whatever objects that array contains, add a property to determine if the condition for the button should be true or false, and then in cellForRowAtIndexPath set the enabled property of the button according to that. When the button is clicked, add a callback to the ViewController(using a delegate) and set the property there.
Sample Code
In custom cell class:
protocol CellButtonDelegate
{
func buttonClicked(cell : PFTableViewCell)
}
public var delegate : CellButtonDelegate?
public var buttonEnabled : Bool?
{
get
{
return heartOutlet.enabled
}
set
{
heartOutlet.enabled = newValue
}
}
#IBAction func heartButton(sender: AnyObject) {
if(parseObject != nil) {
if var votes:Int? = parseObject!.objectForKey("votes") as? Int {
votes!++
parseObject!.setObject(votes!, forKey: "votes")
parseObject!.saveInBackground()
votesLabel?.text = "\(votes!)"
}
}
delegate?.buttonClicked(self)
}
In ViewController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell: FeedTableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? FeedTableViewCell
if(cell == nil) {
cell = NSBundle.mainBundle().loadNibNamed("FeedTableViewCell", owner: self, options: nil)[0] as? FeedTableViewCell
}
if let pfObject = object {
//I took out the irrelevant methods. I can add them if that makes a difference...
var votes:Int? = pfObject["votes"] as? Int
if votes == nil {
votes = 0
}
cell?.buttonEnabled = objects[indexPath.row].isEnabled //The new property you need to add. true by default
cell?.delegate = self //Make sure you implement the delgate
cell?.votesLabel?.text = "\(votes!)"
return cell?
}
func buttonClicked(cell : PFTableViewCell)
{
//Here, get the indexPath using the cell and assign the new property in the array.
}
Please note that the above code is rough. Just get the idea from the code and implement it as per your requirement.