I am trying to load some data from CloudKit to populate a tableview with custom cells and I am having difficulty getting the data to appear.
When I define the number of rows as the count of the CKRecord array the tableview shows up, but with nothing loaded into them. They are just spaced out correctly for the images to be in there. Also, when I set breakpoints at let record = matches[indexPath.row] it won't trigger.
However, if I change the return matches.count to an actual number, the project crashes at let record = matches[indexPath.row]
with the error that the index is out of range. I want to keep the number of rows as the count for the record array, but that is the only change that will actually execute the override function that calls the tableview cell.
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matches.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Card", for: indexPath) as! MatchTableViewCell
let record = matches[indexPath.row]
if let img = record.value(forKey: "Picture") as? CKAsset {
cell.profileimg.image = UIImage(contentsOfFile: img.fileURL.path)
}
return cell
}
Any advice is appreciated
Update- here is where I load the model
func loadModel() {
let totalMatch = (defaults.object(forKey: "passedmatch") as! [String] )
let predicateMatch = NSPredicate(format: "not (UserID IN %#)", totalMatch )
ProfilesbyLocation = defaults.object(forKey: "Location") as! String
let query = CKQuery(recordType: ProfilesbyLocation , predicate: predicateMatch )
publicData.perform(query, inZoneWith: nil,completionHandler: ({results, error in
print("loading")
if (error != nil) {
let nsError = error! as NSError
print(nsError.localizedDescription)
print ("error")
} else {
if results!.count > 0 {
DispatchQueue.main.async() {
self.tableView.reloadData()
self.refresh.endRefreshing()
print("refreshed")
}
}
}
}
)
)}
It sounds like you are not calling
tableview.reloadData()
after your asynchronous data comes in. Leave numberOfRowsInSection as it is and make sure when your network request comes back you are reloading. That should do it.
If you received your data from background thread(or queue), please make sure when you update your UI elements under the main thread(or queue). Try this:
DispatchQueue.main.async {
tableView.reloadData()
}
This solved my problem.
First, disconnect the Delegate/DataSource of the TableView from the Interface Builder refer this image. When you have finally prepared your data that is matches array then add this.
`DispatchQueue.main.async {
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData
}`
Hope this helps.
It looks like your matches is incorrect. Before you call func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
, you should check your matches.
If you are still not sure about that. You can init your matches with string literals and have a try.
Related
I would like to retrieve data from my simple Firestore database
I have this database:
then I have a model class where I have a method responsible for retrieving a data which looks like this:
func getDataFromDatabase() -> [String] {
var notes: [String] = []
collectionRef = Firestore.firestore().collection("Notes")
collectionRef.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
notes = documents.map { $0["text"]! } as! [String] // text is a field saved in document
print("inside notes: \(notes)")
}
print("outside notes: \(notes)")
return notes
}
and as a UI representation I have tableViewController. Let's take one of the methods, for example
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("tableview numberOfRowsInSection called")
return model.getDataFromDatabase().count
}
Then numberOfRows is 0 and the output in the console is:
and I am ending up with no cells in tableView. I added a breakpoint and it doesn't jump inside the listener.
And even though I have 3 of them, they are kinda "late"? They are loaded afterwards. And then the tableView doesn't show anything but console says (later) that there are 3 cells.
If needed, there is also my method for showing the cells names:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cells")
let cell = tableView.dequeueReusableCell(withIdentifier: "firstCell", for: indexPath)
cell.textLabel!.text = String(model.getDataFromDatabase()[indexPath.row].prefix(30))
return cell
}
but this method is not even loaded (no print in the console) and this method is written below the method with numberOfRowsInSection.
I have also 2 errors (I don't know why each line is written twice) and these are:
but I don't think it has something to do with the problem.
Thank you for your help!
As #Galo Torres Sevilla mentioned, addSnapshotListener method is async and you need to add completion handler to your getDataFromDatabase() function.
Make following changes in your code:
Declare Global variable for notes.
var list_notes = [String]()
Add completion handler to getDataFromDatabase() method.
func getDataFromDatabase(callback: #escaping([String]) -> Void) {
var notes: [String] = []
collectionRef = Firestore.firestore().collection("Notes")
collectionRef.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
notes = documents.map { $0["text"]! } as! [String] // text is a field saved in document
print("inside notes: \(notes)")
callback(notes)
}
}
Lastly, call function on appropriate location where you want to fetch notes and assign retrieved notes to your global variable and reload TableView like below:
self.getDataFromDatabase { (list_notes) in
self.list_notes = list_notes
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Changes in TableView:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("tableview numberOfRowsInSection called")
return self.list_notes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cells")
let cell = tableView.dequeueReusableCell(withIdentifier: "firstCell", for: indexPath)
cell.textLabel!.text = String(self.list_notes[indexPath.row].prefix(30))
return cell
}
All you need to do is refresh the table cell every time you retrieve the data. Put this code after you set your data inside the array.
self.tableView.reloadData()
I have an app that has a tableview embedded in a ViewController and whenever I navigate to another ViewController and navigate back to the table view, the cells repeat when I scroll. Does anyone have any advice on how to prevent this? The current code for the tableview is :
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return marathonRaces.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let singleCell: marathonTableViewCell = tableView.dequeueReusableCellWithIdentifier("marathonCell") as! marathonTableViewCell
singleCell.marathonName.text = marathonRaces[indexPath.row]
singleCell.entry.text = "\(entryNumber[indexPath.row])"
singleCell.entries.text = "\(entires[indexPath.row])"
singleCell.length.text = "\(length[indexPath.row])"
return singleCell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = self.marathonsTableView.indexPathForSelectedRow!
let currentCell = marathonsTableView.cellForRowAtIndexPath(indexPath) as! marathonTableViewCell
let marathonEvents = currentCell.marathonName.text
self.performSegueWithIdentifier("marathonDetail", sender: self)
}
I am using swift, xcode7, and parse as my backend
the only relevant code within viewDidAppear would be :
var query = PFQuery(className: “Marathons")
query.orderByAscending("end")
query.findObjectsInBackgroundWithBlock { (marathons, error:NSError?) -> Void in
if(error == nil ){
//success
for marathon in marathons! {
self.marathonRaces
.append(marathon[“marathonName"] as! String)
self.entry.append(marathon[“entryNumber"] as! Int)
self.entries.append(marathon[“entries"] as! Int)
self.length.append(marathon[“length"] as! Int)
}
self.marathonsTableView.reloadData()
}else {
print(error)
}
}
The problem is in your viewDidAppear method. Every time the controller appears you fetch data from background, append them to your arrays and reload the tableview. Move the code for fetching data to viewDidLoad for example and "repeating" should be gone.
Have you checked to see if the datasource marathonRaces is gaining more entries?
You may be adding more entries on each back navigation, if so either do not add them or remove all entries prior to adding them.
I have a function called loadData
func loadData(){
feedData.removeAllObjects()
var findFeedData:PFQuery = PFQuery(className: "userQuotes")
findFeedData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]?, error:NSError?)->Void in
if error == nil{
if let objs = objects{
for object in objs{
let quote:PFObject = object as! PFObject
self.feedData.addObject(quote)
}
let array:NSArray = self.feedData.reverseObjectEnumerator().allObjects
self.feedData = NSMutableArray(array: array)
self.tableView.reloadData()
}
}
}
}
and then in the cellForRowAtIndexPath I actually get the data and display it. I self.loadData() in the viewDidLoad function.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:QuoteTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! QuoteTableViewCell
let quote:PFObject = self.feedData.objectAtIndex(indexPath.row) as! PFObject
cell.contentTextView.text = quote.objectForKey("content") as! String
// Configure the cell...
return cell
}
I am getting no compiler errors and everything seems to be working. The content gets pushed to parse but it doesn't load with these functions. All that runs is an empty table view cells When it should be displaying content from Parse. I feel I am missing something simple logically here... I am a beginner so pardon any silly mistakes. I am following a tutorial but that is a bit outdated.
If you are using a mutable array, you will need to initialize it before adding in objects.
You are changing UI on an asynchronous thread.
The call to parse is async. When this call completes you are reloading your table view.
You should instead dequeu your UI change to the main thread
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
Verify these methods:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1 //or more if you want
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.feedData.count
}
I have a table view, where the cell is clicked on, and it segues into another tableview. The first tableview works and loads the appropriate data. the second does not display the data. Here is my code:
let query = PFQuery(className: "_User")
query.whereKey("Chemistry", equalTo: true)
query.findObjectsInBackgroundWithBlock { (caption: [AnyObject]?, erreur: NSError?) -> Void in
if erreur == nil {
// on a réussi
for caption in caption! {
self.chemistryListe.append(caption["username"] as! String)
}
println("we are here")
println(self.chemistryListe)
}
else {
// quelque chose s'est passé
}
}
And here are my cellforRowatIndexPath and numberOfRowsinSection methods:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.chemistryListe.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SearchResult") as! CompanyTableViewCell
println(self.chemistryListe)
cell.nameLabel.text = chemistryListe[indexPath.row]
return cell
}
if I change this line:
cell.nameLabel.text = chemistryListe[indexPath.row]
to this:
cell.nameLabel.text = "testcell"
then everything works, and it correctly displays the text "testcell". So obviously it is not reading that there is anything in the chemistryListe array. But I set a breakpoint in the middle of my query, and saw that the query was correctly fetching the data that should be in the array, and was putting it into the array. But just the tableview cell isn't reading it. Any idea what could be wrong? thanks
Make sure first if you have data into the array. Then you if the array contains string change this line to :
cell.nameLabel.text = chemistryListe[indexPath.row] as! String
Change
cell.nameLabel.text = chemistryListe[indexPath.row]
to
cell.nameLabel.text = "\(chemistryListe[indexPath.row])"
Found the answer myself:
I just needed to add
self.tableView.reloadData()
to the end of the query method.
thanks to all for the effort.
I'm working on my first Swift app, and stuck on a bug forever. When adding a new entry to the coredata, everything goes fine the first time. However, with additional items, the previously added item is duplicated in the table.
The data is not duplicated, only the cell. When the app is reloaded, the cells are displayed correctly.
Here's the code that populates the cells:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let fetchedSections: AnyObject = fetchedResultController.sections as AnyObject? {
return fetchedSections.count
} else {
return 0
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let fetchedSections: AnyObject = fetchedResultController.sections as AnyObject? {
return fetchedSections[section].numberOfObjects
} else {
return 0
}
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let lookup = sortedArray[indexPath.row] as! NSManagedObjectID
let spot = spotsDict[lookup] as! Spots
let cell = tableView.dequeueReusableCellWithIdentifier("SpotCell", forIndexPath:indexPath) as! SpotCell
println(sortedArray)
println(spot)
cell.spotLabel.text = spot.title
cell.spotPhoto.image = self.imagesDict[lookup]
cell.distanceLabel.text = self.distanceStringDict[lookup] as NSString! as String
cell.spotPhoto.layer.cornerRadius = 4
cell.spotPhoto.clipsToBounds = true
return cell
}
Please replace your code
let lookup = sortedArray[indexPath.row] as! NSManagedObjectID
with below code in cellForRowAtIndexPath method.
let lookup = sortedArray[indexPath.section] as! NSManagedObjectID
The root of the problem was in my sorting method.. simplified the code a lot, and sorted with sortDescriptors instead.