Returning multiple dequeueReusableCells in a TableView, Swift - ios

The problem I'm trying to solve is to return more than one cell for the loaded task, since i cannot iterate without getting a brand new task loaded.
Each view has one or more cell that is supposed to be loaded for that specific task and i can't seem to solve the problem. Thank you for helping out!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let categoryCells = self.tasks[indexPath.row] as? Task
var cellChooser: BoredTaskCell!
if selectedTaskCategory == "Movie"{
let cell = tableView.dequeueReusableCellWithIdentifier("imdbCell") as! BoredTaskCell!
cell!.selectionStyle = UITableViewCellSelectionStyle.None
cellChooser = cell
}
else if selectedTaskCategory == "Lifestyle" {
let emailCell = tableView.dequeueReusableCellWithIdentifier("emailCreatorCell") as! BoredTaskCell!
emailCell!.selectionStyle = UITableViewCellSelectionStyle.None
cellChooser = emailCell
}
else {
let emptyCell = tableView.dequeueReusableCellWithIdentifier("messageCell") as! BoredTaskCell!
cellChooser = emptyCell
emptyCell!.selectionStyle = UITableViewCellSelectionStyle.None
}
return cellChooser
}

The short answer is that you cannot return two cells from one call to cellForRowAtIndexPath. But to achieve what you want, change the way you construct the tableView, so it has one section for each task, rather than one cell for each task. ie use indexPath.section as the index for your array. You will need to amend all the table view data source methods (eg. so numberOfSectionsInTableView will return self.tasks.count). The trick comes in numberOfRowsInSection, which should typically return 1, but for those tasks where you want two cells, it should return 2. Then in cellForRowAtIndexPath, you determine which task to display using indexPath.section. Then if indexPath.row is 0 use one cell type (eg imdbcell), and if it's 1 use the other cell type (eg message cell).

Related

Table view cell doesn't show updated data

I reached a correct value and printed it during the debug sessions. However, when i run the application, the calculated value (newcalory) doesn't show up the specific table cell text field. (aka. cell.itemTotalCalory.text) Do you have any ideas for the solution?
*I attached the related code blocks below.
Thanks a lot,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = ingredientTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! IngredientTableViewCell
cell.ingredientNameTextField.text = ingredients [indexPath.row].ingredientName
cell.numberofItem.text = "1"
let cellcalory = ingredients [indexPath.row].ingredientCalory
cell.itemTotalCalory.text = cellcalory
cell.plusButton.tag = Int(cell.itemTotalCalory.text!)! //indexPath.row
cell.plusButton.addTarget(self, action:#selector(plusAction), for: .touchUpInside)
cell.minusButton.tag = Int(cell.itemTotalCalory.text!)!
cell.minusButton.addTarget(self, action:#selector(minusAction), for: .touchUpInside)
return cell
}
#IBAction func plusAction(sender: UIButton)
{
let cell = ingredientTableView.dequeueReusableCell(withIdentifier: "cell") as! IngredientTableViewCell
let buttonRow = sender.tag
if cell.numberofItem.text == "1" || cell.numberofItem.text != "1"
{
cell.numberofItem.text = "1"
let textValue1 = cell.numberofItem.text
var textValue = Int(textValue1!)
textValue = textValue! + 1
cell.numberofItem.text = String(describing: textValue)
let oldcalory = buttonRow
cell.itemTotalCalory.text = String (((textValue! * Int(oldcalory)) + Int(oldcalory)))
let newcalory = cell.itemTotalCalory.text
refresh(newcalory: newcalory!);
}
}
func refresh(newcalory :String)
{
let cell = ingredientTableView.dequeueReusableCell(withIdentifier: "cell") as! IngredientTableViewCell
cell.itemTotalCalory.text = newcalory
DispatchQueue.main.async {
self.ingredientTableView.reloadData()
}
}
What you should do is to update the value in ingredients array and then call ingredientTableView.reloadData() to reflect this to the UI.
Calling dequeueReusableCell(withIdentifier:) in refresh method will not work as expected for what are you trying to do:
For performance reasons, a table view’s data source should generally
reuse UITableViewCell objects when it assigns cells to rows in its
tableView(_:cellForRowAt:) method. A table view maintains a queue or
list of UITableViewCell objects that the data source has marked for
reuse. Call this method from your data source object when asked to
provide a new cell for the table view. This method dequeues an
existing cell if one is available or creates a new one using the class
or nib file you previously registered. If no cell is available for
reuse and you did not register a class or nib file, this method
returns nil.
So, refresh method should be similar to:
func refresh() {
// updating ingredients array upon reqs satisfaction...
// and then:
ingredientTableView.reloadData()
// nameOfYourRefreshControl.endRefreshing()
}
Also, if you are pretty sure that you want to get a specific cell from the tableView, you might want to use cellForRow(at:) instance method:
Returns the table cell at the specified index path.
func refresh() {
let cell = ingredientTableView?.cellForRow(at: YOUR_INDEX_PATH)
//...
}
Hope this helped.
I found the solution, the lines that are listed below are useless.
let cell = ingredientTableView.dequeueReusableCell(withIdentifier: "cell") as! IngredientTableViewCell
cell.itemTotalCalory.text = newcalory
I updated the ingredient array with the new value inside the plusAction function and my problem solved. Thanks for all postings.

In Swift, I'd like the last 3 sections have only labels in their rows (and NOT text fields)

In my Swift project I have a table controller view with a table view inside. The table view is divided into 4 section and every section has 4 rows. Currently, each row is formed by a label beside a text field.
My purpose is that only rows in the first section has label beside text field. On the contrary, I want the last 3 sections have only labels in their rows (and NOT text fields).
Please, help me.
That's the code I wrote to manage with this problem but it's not working:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("index ", indexPath.section);
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath)
cell.textLabel?.text = self.items[indexPath.section][indexPath.row];
if(indexPath.section == 0){
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell") as! TextInputTableViewCell
cell.configure("", placeholder: "name")
}
return cell
}
It's not working because you are using the same cell for all the rows. You need to define two different rows. You can do this by setting prototype cells to more than one row (two in your case).
Each cell must have its own reuse identifier and it must be unique within that table view.
Then in your tableView(cellForRowAtIndexPath:) you can ask:
if indexPath.section == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("firstSectionCell", forIndexPath: indexPath)
//
} else {
cell = tableView.dequeueReusableCellWithIdentifier("otherSectionCell", forIndexPath: indexPath)
//
}
Also note that in Swift you do not need to use parenthesis in if-statement (nor for, while, etc). So I suggest you remove them as they are pointless.
It looks like your cell in if(indexPath.section == 0) block doesn't actually have scope outside that block so any properties set there won't get returned there. If you just want to remove the textField, but keep the label, You can just set the textField.hidden = true. Here is how I would go about it.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath) as! TextInputTableViewCell
cell.textLabel?.text = self.items[indexPath.section][indexPath.row];
if(indexPath.section == 0){
cell.textField.hidden = false //Assumes TextInputTableViewCell's textField property is called "textField"
cell.configure("", placeholder: "name")
} else {
cell.textField.hidden = true //Not in first section we will hide textField
}
return cell
}
Doing it this way you can use the same cell class for every cell in your tableView, but just hide what you want based on its section.

Custom UITableviewCells displayed correctly but only one in the section is the correct class

I apologize for the poorly worded question but I simply cannot think of why/how to describe this bug in a short manner.
Basically I have a table view controller displaying two sections, each with a respective custom UITableViewCell subclass. They are displayed perfectly, with the correct model data.
The problem arises when I hit the "done" button, my program traverses through all the cells, gathering the data from each cell and updating the model. The first section goes off without a hitch... but the second section is where trouble hides. The first cell of this section can be casted into the proper subclass, yet any other additional cells don't pass this conditional test and their reuse identifier is nil. I tried checking the cells when they're dequeued but they are being created/reused properly. My code is below and any suggestions will aid my sanity.
Here is the part of my helper function that traverses the cells:
for i in 0..<tableView.numberOfSections {
for j in 0...tableView.numberOfRowsInSection(i) {
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: j, inSection: i))
// This section seems to work fine
if(i == 0){
if let gradeCell: PercentTableViewCell = cell as? PercentTableViewCell {
print("retrieveCellInfo: gradeCell - \(gradeCell.getPercent())")
newPercentages.append(gradeCell.getPercent())
}
} else if (i == 1){
print("Else if i == 1 : j == \(j) : \(cell?.reuseIdentifier!)") // Prints "category" for the first cell and "nil" for any other
// This is the cast conditional that only lets one cell through
if let catCell = cell as? EditTableViewCell{
newCategories.append(Category(name: catCell.category!, weight: catCell.weight!, earned: catCell.earned!, total: catCell.total!))
}
}
}
}
Here is my delegate function which works perfectly as far as I can tell. As I said, the UI is displayed correctly:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(indexPath.section == 0){
/* Grade Parameter Cells */
let cell: PercentTableViewCell = tableView.dequeueReusableCellWithIdentifier("gradeParam", forIndexPath: indexPath) as! PercentTableViewCell
var gradePercentages = course?.getGradePercentages()
// Make sure percentages array is sorted correctly
gradePercentages?.sortInPlace() {
return $0 > $1
}
// Update cell's member variables
cell.initialize(letters[indexPath.row], percent: gradePercentages![indexPath.row])
return cell
} else {
/* Category Cells */
let catcell: EditTableViewCell = tableView.dequeueReusableCellWithIdentifier("category", forIndexPath: indexPath) as! EditTableViewCell
let category = categories![indexPath.row]
catcell.setLabels(category.name, earned: category.earned, total: category.total, weight: category.weight)
return catcell
}
}
The problem arises when I hit the "done" button, my program traverses
through all the cells, gathering the data from each cell and updating
the model
This is your problem. Your cells should reflect your data model, they cannot be relied upon to hold data as once the cell is offscreen it may be re-used for a row that is onscreen.
If data is updated by the user interacting with the cells then you should update your data model as they do that. You can use a temporary store if you don't want to commit changes immediately.

Two TableViews One Data Source?

I have setup a segue that will show a view controller with a small TableView. I want a different segue to show a bigger TableView but I want the bigger table to have the same exact info as the smaller table. Got the smaller tableView working perfect on its own, but once I give the bigger table a Data source, reset and try it out.....crashes.
// IndexPath or First Cell in TableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if self.TaskTableViews.hidden == false {
cell = tableView.dequeueReusableCellWithIdentifier( "FirstTask" , forIndexPath: indexPath) as UITableViewCell!
let list = frc.objectAtIndexPath(indexPath) as! List
cell.textLabel?.text = list.taskName
cell.textLabel?.textColor = UIColor.whiteColor()
TaskTableViews.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.55)
TaskTableViews.layer.cornerRadius = 8
TaskTableViews.separatorColor?.colorWithAlphaComponent(2.0) }
if self.TaskTable2.hidden == false {
cell = tableView.dequeueReusableCellWithIdentifier( "Second Task" , forIndexPath: indexPath) as UITableViewCell!
let list = frc.objectAtIndexPath(indexPath) as! List
cell.textLabel?.text = list.taskName
cell.textLabel?.textColor = UIColor.whiteColor()
TaskTable2.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.55)
TaskTable2.layer.cornerRadius = 8
TaskTable2.separatorColor?.colorWithAlphaComponent(2.0) }
return cell as UITableViewCell
}
The problem is that the code for your two tables is slamming into each other. To fix this, rejigger your logic. Do not make your logic depend on what is hidden. You can handle only one table at a time; just one table is calling here. That table comes in as the tableView parameter. Make your logic depend on that. Depending what table view that tableView parameter is, configure the cell and return it for that table view.
I think that you should consider changing your approach by just resizing the table view dynamically when you go from one scene to the other instead of having two table views if the information is exactly the same.
If you're still pushing for this approach then don't make the condition be that the table view is hidden and instead implement your own logic or boolean to determine this. But again, I'd rather resize a single table view as needed.
You have many problems in your code
1.
var cell = UITableViewCell()
What's the point of this line?
2.
cell = tableView.dequeueReusableCellWithIdentifier( "FirstTask" , forIndexPath: indexPath) as UITableViewCell!
What's the point of casting? This function returns UITableViewCell (not even optional)
3.
cell.textLabel?.text = list.taskName
Should not compile, cause UITableViewCell doesn't have textLabel
4.
TaskTableViews.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.55)
What's the point of doing this every cell request? Move this to viewDidLoad or other appropriate place
Your if { } parts are identical except of reuse identifier
Use tableView argument when needed
Use if { } else
8.
return cell as UITableViewCell
Why cast? Just return
I'm sure I haven't found all of them))

How to display two cells each with different functionality in the same tableView simultaneously?

I have tableView.
I have tableViewCellOne. tableViewCellOne allows text input and image input by the user which gets saved in a database. This part works fine.
I want to build tableViewCellTwo. This cellTwo has a different function than cellOne. cellTwo will retrieve this data and display it.
I want both cells to be visible at the same time. tableViewCellOne above, and tableViewCellTwo below, on the same tableView. Here is the relevant portion of the code I wrote. The way that it works as of now, it only displays tableViewCellOne which is not my intent. This code is written in my TableViewController.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
if (indexPath.row == 0) {
let cellIdentifier = "MyTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MyTableViewCell
// Fetches the appropriate data for the data source layout.
let data = myData[indexPath.row]
cell.MyData1.text = data.1
cell.MyData2.image = data.2
cell.MyData3.text = data.3
return cell
} else {
let cellIdentifier = "TheirDataTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TheirDataTableViewCell
// Fetches the appropriate data for the data source layout.
let data = theirData[indexPath.row]
cell.TheirData1.text = data.1
cell.TheirData2.image = data.2
cell.TheirData3.text = data.3
return cell
}
}
With this if else statement, I am trying to say: when we are loading cell 1 then load the following data, when we are loading cell2 then load the following other data. But I think instead its interpreting it as: if (something) load cell1, if (something else) load cell2. I could be way off base with that diagnosis though.
How do I display two cells each with different functionality in the same tableView simultaneously?

Resources