How to filter UITableViewCell on two variables equalling eachother - swift

I want to populate a UITableView in Swift 4 and I want to just show records that belong to the user that is logged in. Obviously I require an IF statement to see whether two values are equal to each other. How do I return null as such, i.e. no cell is added to the table. Please see the code below
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier:
"RunCell") as? RunCell else { return UITableViewCell() }
let user_id_main = String(MainMenuViewController.myVariables.user_id.prefix(2))
if (user_id_main==runs[indexPath.row].user_id)
{
cell.idLbl.text = "ID: " + runs[indexPath.row].run_id
cell.dateLbl.text = "Date: " + runs[indexPath.row].date_of_run
return cell
}
return cell
}
I know return cell outside of the IF still would return a cell, this is until I find an answer.

This is not the way UITableViews work. You can't decide on the fly if a cell is to be displayed or not. You need to do that before the table is loaded or reloaded.
Before the tableView is loaded or reloaded, filter your array into a new array that just contains records with the matching user_id. Then use this new array as the model for your table. The size of the new array will determine the number of rows in the table, and each row will correspond to one item of your new array.
Every time the user_id_main changes, refilter your runs array and reload the table.

you cannot return nil value cell,
to achieve what you want, first filter your runs array for the values you required and store in other array say mainUserRuns, then use this new array to provide data for your tableview.

Related

Data persistence with reusable cells

I have a large table designed to update a customers data protection preferences.
Some of the table is populated with reusable cells that contain a variable number of checkboxes, and depending on the json returned from the server, some of these checkboxes may be pre-checked.
When I pass the pre-checked state to the cell from tableView cellForRowAt all is well (checkboxes that are pre-checked are pre-checked). The problem I have is that these are reusable cells, and after a user has changed their selections, scrolling up or down the table triggers more calls to the setupCell function, which then resets the checkboxes to their original pre-checked state.
So, the question I have is...
What are the options for me to preserve a user’s selections after they have scrolled a table with resuable cells?
The switch statement in setupCell currently sets the pre-selections with the call to updateSelections(). Obviously this is the cause of the issue and I'm not entirely happy with placing logic directly in the cell anyway, but where is the best place to perform this logic only once? Or, is using reusable cells the wrong approach entirely to have pre-selections?
Any suggestions welcome. Here's a small code snippet to illustrate the point:
// UITableViewDataSource - passing the previous selections to setupCell in the UITableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let viewModel = viewModels[indexPath.row]
switch viewModel {
case .preferences(let preferenceId, let titleText, let isEnabled):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Preferences") as? MarketingChannelPreferencesTableViewCell else {
return UITableViewCell()
}
cell.setupCell(id: id, text: text, isPreChecked: isPreChecked)
return cell
}
}
// UITableViewCell
func setupCell(id: String, text: String, isPreChecked: Bool) {
switch id {
case "email":
emailSelected = isPreChecked
updateSelections(id: id, isPreChecked: emailSelected)
case "post":
postSelected = isPreChecked
updateSelections(id: id, isPreChecked: postSelected)
case "text":
textSelected = isPreChecked
updateSelections(id: id, isPreChecked: textSelected)
default:
break
}
}
viewModels hold the information needed to setup each cell right? And you receive the viewModels from the service?
If so, when the user changes a specific checkbox, you should update the according viewModel. Thus, when you call setupCell inside cellForRowAt you should pass the updated info of each viewModel, resulting in the correct state of each checkbox.
You should make some action method for your checkbox buttons that you put on the MarketingChannelPreferencesTableViewCells and change your viewModels based on changing the value of these checkboxes. So when the cells data reload with user scrolling, cells show the new datas of viewModel
There're several ways.
I've created a small project maybe it will give you an solution to the problem.
https://drive.google.com/open?id=1d_RFdr6luNvRTdSC6XNE2vRWTi2IRyuT

parse: check column then use data in that row

Is it possible to check the Parse column "ID" for 0 (any number) then if 0 (the one in the column) equals indexPath.row 0 (the first cell) it displays the data from the row of 0 in Parse? The picture above is the class for the viewcontroller, the tableview cell data is in another class.
ID: number that will be checked with the indexPath.row
navTitle: navigation bar title
articleTitle: UILabel
written: UILabel
date: UILabel
article: UITextView
Edit for possible duplicate: It is not a duplicate, the other question answers how to move data from a cell to a view controller using prepareForSegue. This question is asking how, if possible, to check a column on Parse and if a number in that column matches an indexPath.row it will use the data from that row to which the number corresponds.
I now understand you would like to show the data that is appropriate for a given index path in a detail view controller, after they tap on the cell at said index path. This is assuming you are correctly showing the appropriate data in the table view, and now the question is how to obtain and show the relevant data for the row the user tapped on in a new view controller.
To do this, you could add an internal property to your detail view controller that is an Int. Set this property to the table view's selected index path's row before the detail view controller is shown, perhaps in prepareForSegue for example.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detailVC = segue.destinationViewController as? DetailViewController {
if let selectedIndexPath = self.tableView.indexPathForSelectedRow {
detailVC.integerIdentifier = selectedIndexPath.row
}
}
}
Now in that detail view controller, you can access the property's value and query for the information you need from Parse given that value. (And you'll likely want to display some feedback to the user to inform them you're downloading the appropriate data to display.) For example, you could have the following code in a function you call from viewDidLoad:
let query = PFQuery(className: "eventsDetail")
query.whereKey("ID", equalTo: self.integerIdentifier)
query.getFirstObjectInBackgroundWithBlock { (detail: PFObject?, error: NSError?) -> Void in
//update your labels etc using the detail (Optional) PFObject
}
I want to note, importantly, that this is not a great way to store the data in Parse. One should not tie the data to how it will be displayed, which is the case here tying an identifier to the index path, forcing item with ID 0 to magically be the article details appropriate for the first cell in the table. You may wish to revisit the database design. For example, perhaps your other class that is used to generate a list of events could have a pointer to the event detail class, which would allow you to obtain the event details before you select an event and not have to query again for details.
Original Answer:
I understand you would like to show the data in this Parse class in your app in a table view, where the first table view cell displays the data for ID 0, the second cell displays the data for ID 1, etc.
To accomplish this, you'll want to query this class and apply an ascending order on the "ID" column. You will get back an array of PFObjects that are sorted as desired - lowest to highest ID, which you can use for your table view's data source to map them one-to-one.
Your query may look something like this:
let query = PFQuery(className: "Article")
query.orderByAscending("ID")
query.findObjectsInBackgroundWithBlock { (articles: [PFObject]?, error: NSError?) -> Void in
//use articles array as the data source, update the interface
self.articles = articles
self.tableView.reloadData()
}
Now in your table view data source methods, use this array to populate the table:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.articles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let article = self.articles[indexPath.row]
//... configure cell, show article title, etc, return cell
}

Tableview data won't print using NSUserDefaults

I am trying to get my saved array in NSUserDefaults and add it to my tableview... When I run print(notifications[indexPath.row])my array (notifications) contents get printed. However when I go to add these contents to my tableview, nothing shows up. I'm assuming that although the array gets saved and retrieved via NSUserDefaults, it may not be able to convert the array contents to a string I can add to my tableview. Any help would be appreciated. Thanks!
Here is some extra code that may be useful:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = notificationsTableView.dequeueReusableCellWithIdentifier("notificationsCell") as! NotificationsTableViewCell
//Set notificationsLabel of cells
print(notifications[indexPath.row])
cell.notificationsLabel.text = notifications[indexPath.row] as? String
//Set datesLabel of cells
cell.datesLabel.text = dates[indexPath.row] as? String
return cell
}
Fix: I had to specify that the array would only be composed of String. To do this I initialized the array via var notifications = [NSString](). Then I could use the append command to edit that array. Finally, to add the saved content into the array I ran, notifications = NSUserDefaults.standardUserDefaults().objectForKey("notificationsKey") as! [NSString].

Selecting Multiple Table View Cells At Once in Swift

I am trying to make an add friends list where the user selects multiple table view cells and a custom check appears for each selection. I originally used didSelectRowAtIndexPath, but this did not give me the results I am looking for since you can highlight multiple cells, but unless you unhighlight the original selected row you cannot select anymore. I then tried using didHighlighRowAtIndexPath, but this doesn't seem to work because now I am getting a nil value for my indexPath. Here is my code:
override func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! AddedYouCell
let currentUser = PFUser.currentUser()?.username
let username = currentCell.Username.text
print(currentCell.Username.text)
let Friends = PFObject(className: "Friends");
Friends.setObject(username!, forKey: "To");
Friends.setObject(currentUser!, forKey: "From");
Friends.saveInBackgroundWithBlock { (success: Bool,error: NSError?) -> Void in
print("Friend has been added.");
currentCell.Added.image = UIImage(named: "checked.png")
}
}
How can I solve this? Thanks
I'm not going to write the code for you, but this should help you on your way:
To achieve your goal, you should separate the data from your views (cells).
Use an Array (i.e. friendList) to store your friend list and selected state of each of them, and use that Array to populate your tableView.
numberOfCellsForRow equals friendList.count
In didSelectRowAtIndexPath, use indexPath.row to change the state of your view (cell) and set the state for the same index in your Array
In cellForRowAtIndexpath, use indexPath.row to retrieve from the Array what the initial state of the cell should be.

tableView.cellForRowAtIndexPath(indexPath) return nil

I got a validation function that loop through my table view, the problem is that it return nil cell at some point.
for var section = 0; section < self.tableView.numberOfSections(); ++section {
for var row = 0; row < self.tableView.numberOfRowsInSection(section); ++row {
var indexPath = NSIndexPath(forRow: row, inSection: section)
if section > 0 {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCell
// cell is nil when self.tableView.numberOfRowsInSection(section) return 3 for row 1 and 2
// ... Other stuff
}
}
}
I'm not really sure what I'm doing wrong here, I try double checking the indexPath row and section and they are good, numberOfRowsInSection() return 3 but the row 1 and 2 return a nil cell... I can see my 3 cell in the UI too.
Anybody has an idea of what I'm doing wrong?
My function is called after some tableView.reloadData() and in viewDidLoad, is it possible that the tableview didn't finish reloading before my function is executed event though I didn't call it in a dispatch_async ??
In hope of an answer.
Thank in advance
--------------------------- Answer ------------------------
Additional explanation :
cellForRowAtIndexPath only return visible cell, validation should be done in data model. When the cell is constructed in
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
It should change itself according to the validation state.
As stated in the documentation, cellForRowAtIndexPath returns:
An object representing a cell of the table, or nil if the cell is not visible or indexPath is out of range.
Hence, unless your table is fully displayed, there are some off screen rows for which that method returns nil.
The reason why it returns nil for non visible cells is because they do not exist - the table reuses the same cells, to minimize memory usage - otherwise tables with a large number of rows would be impossible to manage.
So, to handle that error just do optional binding:
// Do your dataSource changes above
if let cell = tableView.cellForRow(at: indexPath) as? MyTableViewCell {
// yourCode
}
If the cell is visible your code got applied or otherwise, the desired Cell gets reloaded when getting in the visible part as dequeueReusableCell in the cellForRowAt method.
I too experienced the issue where cellForRowAtIndexPath was returning nil even though the cells were fully visible. In my case, I was calling the debug function (see below) in viewDidAppear() and I suspect the UITableView wasn't fully ready yet because part of the contents being printed were incomplete with nil cells.
This is how I got around it: in the viewController, I placed a button which would call the debug function:
public func printCellInfo() {
for (sectionindex, section) in sections.enumerated() {
for (rowIndex, _) in section.rows.enumerated() {
let cell = tableView.cellForRow(at: IndexPath(row: rowIndex, section: sectionindex))
let cellDescription = String(describing: cell.self)
let text = """
Section (\(sectionindex)) - Row (\(rowIndex)): \n
Cell: \(cellDescription)
Height:\(String(describing: cell?.bounds.height))\n
"""
print(text)
}
}
}
Please note that I'm using my own data structure: the data source is an array of sections, each of them containing an array of rows. You'll need to
adjust accordingly.
If my hypothesis is correct, you will be able to print the debug description of all visible cells. Please give it a try and let us know if it works.