replacing a cell on the fly in a static UITableView - ios

in an App I am writing, i have a form built using storyboard around a UITableView with static cells in a UITableViewController. The table has 6 rows that collects different bits of information. Row 0,3,4,5 have textfield/textview/labels to collect/display some information, row 1 has a UICollectionView with a dozen or so icons. row 2 has a height of 50 but is empty for now.
this setup works well but i am trying to add some functionality and i am stumped.
depending on what I select in the UICollectionView (in row 1), i would like to load one of a number of different cells in row 2
for example, if i click on the 2nd icon (blood pressure), the cell to load in row 2 will be used to input the systolic and diastolic values. if I click on the 3rd icon (temperature), the cell to load in row 2 will be used to input the temperature, etc...
i've designed the cells in separate class files of UITableViewCell and the corresponding xib files. i know how to register them using
tableView.register(UINib(nibName: "myBPTableCell", bundle: nil), forCellReuseIdentifier: "myBPTableCell")
just not sure where to put the logic to update the table on the fly.
if eventTypeCollectionIndex == 4 {
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
return cell
}
i was thinking in
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
but since this function has to return a cell, i am not sure how to return the pre-existing cells for anything other than row 2
thanks
Sami

You would want to define different cases in the cell for row at index method. And then when the user taps a button, reload the cell that you would want to change.
You would either use reload rows or self.tableView.reloadData()
So for example:
var didTap = false
func buttonTapped() {
didTap = true
// Reload table
}
And then either use a different nib for the cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if didTap {
// show new appearance
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
return cell
} else {
// show original
let cell2 = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell2", for: indexPath) as! myBPdTableCell2
return cell2
}
}
Or you could update the properties of the same nib class:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
if didTap {
// show new appearance
cell.backgroundColor = .white
} else {
// show original
cell.backgroundColor = .black
}
return cell
}
Edit based on comment:
You can also just do the following, where you have one cell for row 2 and then another cell for the other rows:
if indexPath.row == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
if didTap {
cell.backgroundColor = .white
} else {
cell.backgroundColor = .black
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "myOtherCell", for: indexPath) as! myOtherCell
return cell
}

Related

How to center a spinner with cell content view after removing a cell accessory?

I have a tableview in my storyboard where the prototype cell has a disclosure indicator by default.
When I populate my table I want to remove the indicator only from the last cell AND center a spinner on it.
I'm doing it like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterCell", for: indexPath) as! CharacterCell
if indexPath.row == charactersViewModel.charactersCount - 1 {
cell.accessoryType = .none
cell.accessoryView = .none
// Spinner
let spinner = UIActivityIndicatorView(style: .large)
spinner.color = .white
spinner.center = cell.contentView.center
cell.contentView.addSubview(spinner)
spinner.startAnimating()
}
return cell
}
The problem is that the spinner is offcenter, a little bit to the left, just like if the accessory is still there, but hidden.
I feel maybe I'm missing the lifecycle of a table cell, maybe it's getting the center value of the content view when the accessory is still there, so when it's removed it is offcenter?
I tried on willDisplay as well but the same thing happens.
Any tips on this?
As #Paulw11 mentioned, I used a second subclass and created another cell prototype in my tableview.
Then when the last position at the table is reached, we can use the second prototype on cellForRowAt.
Here how it is:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row >= charactersViewModel.charactersCount - 1 {
reloadRows(indexPath: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: "LoadingCharacterCell", for: indexPath) as! LoadingCharacterCell
cell.startSpinner()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterCell", for: indexPath) as! CharacterCell
cell.configureCell(charactersViewModel: charactersViewModel, cell: cell, index: indexPath.row)
return cell
}
}
private func reloadRows(indexPath: IndexPath) {
var indexPathList = [IndexPath]()
indexPathList.append(indexPath)
charactersTableView.reloadRows(at: indexPathList, with: .automatic)
}
And with the reloadRows function, the last cell is updated and removed when the table receives more data.

Swift Custom Tableview Cell with UITableViewCellStyle

I am trying to make a custom table view cell.
If I do this:
class TableViewCell: UITableViewCell {
#IBOutlet weak var cellBackgroundImage : UIImageView!
}
And:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
cell.cellBackgroundImage.backgroundColor = UIColor.white
cell.cellBackgroundImage.layer.masksToBounds = false
cell.cellBackgroundImage.layer.cornerRadius = 5
let event = self.fetchedResultsController.object(at: indexPath)
self.configureCell(cell, withEvent: event)
return cell
}
I obtain a white rounded cell background. Easy. And I can use the original cell.textLabel.text.
Perfect.
But, if I want to do something more complex:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TableViewCell
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "Cell") as? TableViewCell
cell?.cellBackgroundImage.backgroundColor = UIColor.white
cell?.cellBackgroundImage.layer.masksToBounds = false
cell?.cellBackgroundImage.layer.cornerRadius = 5
self.configureCell(cell!, withObject: object)
}
And at the same time to use the original table view properties as UITableViewCellStyle.subtitle and cell.accessoryView, the app crashes or shows the wrong output.
THIS MEANS THAT I MUST USE A FULL CUSTOM CELL WITH MORE OUTLETS TO REPLACE THE ORIGINAL ELEMENTS AS UITableViewCellStyle.subtitle and cell.accessoryView ???
I will express it in a different way:
Can I use a custom tableview cell only for one purpose (like the rounded background) and use the original elements such as the subtitle style and the accesory view?
In afirmative case, how?

Persist UITableViewCell background color on scrolling tableViewCells

I have a UITableView, when the user taps a cell it turns grey to indicate it is complete. However when the cell is scrolled out of view, when it comes back into view it has returned to its default background colour erasing the users interaction.
Is there a way to persist the cell tapped background color on scrolling?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
if cell.backgroundColor == UIColor.customExerciseDoneCellColor() {
cell.backgroundColor = UIColor.customBackgroundGraphite()
} else if cell.backgroundColor == UIColor.customBackgroundGraphite() {
cell.backgroundColor = UIColor.customExerciseDoneCellColor()
}
}
}
The issue is when you scroll after selecting the cell, the selected colour is not retained isn't it??
Cell for row delegate is called whenever you scroll. So even though you set background in Did select delegate its changed when Cell for row is called.
In order to tackle this situation add a boolean to tableview cell and when you select the cell change the boolean status.
In cell for row add this:
if cell.CheckBoolean == true{
cell.backgroundcolor = UI.color()
}else{
cell.backgroungcolor = UI.color()
}
In did select delegate :
change the value of that boolean and reload the table view

Trouble making more than one or two cells into UITableView with different sizes Swift

I'm trying to build two (or more) custom cells into table View and without custom classes for each one, I'm using storyboard to cell prototyping and tableView.dequeueReusableCell(withIdentifier: "customCellName", for: indexPath) to create different cell from cellForRowAt method.
I need cell type 1 with different height than cell type 2 but if a try to use func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat the cell type 2 is not displayed correctly.
I try to build a "dummy project" to understand the right way, here my code:
var cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let value = dataArray[indexPath.row]
if (value == "second") || (value == "Hawaii") {
cell = self.tableView.dequeueReusableCell(withIdentifier: "cellTwo", for: indexPath)
}
let lbl = cell.viewWithTag(100) as! UILabel
lbl.text = dataArray[indexPath.row]
return cell
In ViewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.estimatedRowHeight = 200
}
This is the result:
Every cell with the same size, is not working for me.
If you want to create a custom cell appearance, you will have to create a subclass of UITableViewCell - there are some great tutorials on how to do that.
You then have to register your custom cell, so that your UITableView knows about it. See register(_:​for​Cell​Reuse​Identifier:​) method.
In your table​View(_:​height​For​Row​At:​) you can do write the following code in order to figure out the height of particular cell:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
let value = dataArray[indexPath.row]
if (value == "second") || (value == "Hawaii") {
return 180.0
} else {
return 40.0
}
}

How to correctly implement a table view with a fixed number of custom cells?

I have a UITableView I need to have just 3 cells. This is what I have in IB: . All cells contain a UITextField, and that's why are using custom cells. Each cell is binded to a CustomCell class with the corresponding UITextField as an outlet. I also have protocols to pass the texts in the UItexField to the table's UIViewController.
In the UIViewController I have this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
switch indexPath.row {
case 0:
cell = tableView.dequeueReusableCell(withIdentifier: "firstNameCell", for: indexPath)
(cell as! FirstNameTableViewCell).delegate = self
case 1:
cell = tableView.dequeueReusableCell(withIdentifier: "lastNameCell", for: indexPath)
(cell as! LastNameTableViewCell).delegate = self
case 2:
cell = tableView.dequeueReusableCell(withIdentifier: "birthdateCell", for: indexPath)
(cell as! BirthdateTableViewCell).delegate = self
default:
break
}
return cell
}
But here:
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FirstNameTableViewCell.self, forCellReuseIdentifier: "firstNameCell")
tableView.register(LastNameTableViewCell.self, forCellReuseIdentifier: "lastNameCell")
tableView.register(BirthdateTableViewCell.self, forCellReuseIdentifier: "birthdateCell")
}
The contents of the cells are not shown.
Which is the correct way to deal with this scenario?
As Sandeep Bhandari said, if you want to keep static cells, then use static cells in tableview:
You can specify number of rows, sections, and number of rows in each sections etc. there in storyboard itself.

Resources