problems with UITableView and reuseablecells - swift

so i am experiencing two problems right now with my (pretty basic) tableview. i will paste my code below but here is the general gist: i'm making a dictionary app, so each cell contains two labels for the two languages. if a cell gets tapped, i'd like for: 1. cell height increase, 2. font size increase, 3. text align center, 4. cell background change color.
i've also included code to 'reset' a selected cell if it is selected a second time - this seems to be causing my second problem
the problem is when a cell is selected:
those four things happen to cells which are currently offscreen.
the program crashes when: i select cell 0, the cell changes which is fine, but when i click on any cell that is offscreen at the time (say cell 10) the program will crash giving me this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// reset previous cell but only if selectedRow isn't -1 which is initial value
if selectedRowIndex != -1{
let previousCell = convoTable.cellForRowAtIndexPath(NSIndexPath(forRow: selectedRowIndex, inSection: 0)) as! PhraseTableViewCell
previousCell.backgroundColor = UIColor.whiteColor()
previousCell.lang2Label.font = UIFont(name: (previousCell.lang2Label?.font?.fontName)!, size: (previousCell.lang2Label?.font?.pointSize)! - 5)
previousCell.lang2Label.textAlignment = .Right
}
//make changes to new cell that was pressed upon
if selectedRowIndex != indexPath.row{
print("new cell got selected")
cellSelected = true
// save the selected index
self.selectedRowIndex = indexPath.row
let cell = self.convoTable.cellForRowAtIndexPath(indexPath) as! PhraseTableViewCell
cell.lang2Label.font = UIFont(name: (cell.lang2Label?.font?.fontName)!, size: (cell.lang2Label?.font?.pointSize)! + 5)
cell.backgroundColor = UIColor.cyanColor()
cell.lang2Label.textAlignment = .Center
// update the height for all the cells
self.convoTable.beginUpdates()
self.convoTable.endUpdates()
cell.lang1Label.frame = CGRectMake(3,0, cell.bounds.width, cell.bounds.height/3)
cell.lang2Label.frame = CGRectMake(-3,cell.bounds.height/3, cell.bounds.width, cell.bounds.height/3 * 2)
}
else {
print("same cell pressed")
cellSelected = false
selectedRowIndex = -1
}
self.convoTable.beginUpdates()
self.convoTable.endUpdates()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PhraseTableViewCell
cell.lang1Label.text = items![indexPath.row].eng
cell.lang2Label.text = items![indexPath.row].prs
return cell
}
thank you all very much, this help is much appreciated.

so there is no real answer to the questions i posed, the best workaround is to just have another array which maintains the states in the cells. sucks i know but really the best "solution"

Related

How to fix UITableViewCell shrinking on certain devices?

I'm working on an app that will show notifications, allow the user to post comments and edit the notification and see comments on the notification.
To do this I have 3 Prototype cells called "Active", "Control" and "Comment" cells. My first two Active and Control look and work fine with their auto-sizing on all devices. The comment cell, however, shrinks on Plus-sized iOS Devices, not including iPads to an unreadable size.
The comment cell simply has 3 labels of slightly different sizes and colors inside of a Stack View. This is on Swift 4.
The list of devices this happens on:
6+, 6s+, 7+, 8+, X, Xs, Xs Max
https://imgur.com/a/QHPTlSW
The above shows the cell as expected on an iPhone XR and as unexpected on an Xs Max
I've tried editing the compression and hugging rules, tried to use a constraint to force the height (which technically worked however it ruined the look and threw a warning).
func numberOfSections(in tableView: UITableView) -> Int {
if activeEvents.count == 0 {
self.tableView.isHidden = true
self.activityIndicator.isHidden = false
self.activityLabel.isHidden = false
self.activityIndicator.startAnimating()
} else {
self.tableView.isHidden = false
self.activityIndicator.isHidden = true
self.activityLabel.isHidden = true
self.activityIndicator.stopAnimating()
}
return activeEvents.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if activeEvents[section].expanded {
return activeEvents[section].comments.count + 2
} else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let activeEvent = activeEvents[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "activeCell") as! ActiveCell
cell.selectionStyle = .none
cell.setCell(active: activeEvent)
return cell
} else if indexPath.row == 1 {
let activeEvent = activeEvents[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "controlCell") as! ControlCell
cell.setNoti(active: activeEvent)
cell.selectionStyle = .none
cell.delegate = self
cell.vc = self
return cell
} else {
let activeEvent = activeEvents[indexPath.section]
let activeComment = activeEvent.comments[indexPath.row - 2]
let cell = tableView.dequeueReusableCell(withIdentifier: "commentCell") as! CommentCell
cell.setCell(comment: activeComment)
return cell
}
}
One thing you can do is add a stackView in your cell, then add your text label inside said stackView, just pin everything to the edges and DON'T set the height constraint in neither of the elements, that should auto resize the cell accordingly, also I don't see it in your code, but I assume you are implementing the tableView.rowHeight = UITableViewAutomaticDimension in your viewdidload()
You just need to check your constraints, as I said for automatic dimension to work you need to NOT set height constraints on the cells, here is a link that you'll find helpful raywenderlich.com/8549-self-sizing-table-view-cells

adding Button in last tableView Cell shown twice when scrolling

Using Swift4, iOS11.2.1, Xcode9.2,
I successfully added a custom button to the last cell of a tableView. (the button is used to add cells in the tableView - this also works fine...) - see Screenshot-1.
But now the issue: When adding more cells (i.e. more than fit in the tableView-height), and if you scroll to the top, then there is a second cell that shows this button - see Screenshot-2.
(see code below....)
Here are the two screenshots:
How can I get rid of the extra button ????
(it seems that a cell is reused and the button is partly drawn. The cut-off can be explained since the height of the last cell is made bigger than the other cells. But still, there shouldn't be parts of buttons in any other cell than the very last one...even when scrolling up).
Any help appreciated !
Here is my code (or excerts of it...):
override func viewDidLoad() {
super.viewDidLoad()
// define delegates
self.fromToTableView.dataSource = self
self.fromToTableView.delegate = self
// define cell look and feel
self.addButton.setImage(UIImage(named: "addButton"), for: .normal)
self.addButton.addTarget(self, action: #selector(plusButtonAction), for: .touchUpInside)
//...
self.fromToTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let resItems = resItems else {
self.rowCount = 0
return 0
}
self.rowCount = resItems.count - 1
return resItems.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if(indexPath.row == (self.rowCount)) {
return 160
} else {
return 120
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = fromToTableView.dequeueReusableCell(withIdentifier: "SettingsCell") as? SettingsCustomTableViewCell
// configure the cell
cell?.configureCell(tag: indexPath.row)
// fill cell-data
if let resItem = self.resItems?[indexPath.row] {
cell?.connectionItem = resItem
}
// add addButton to last cell
if(indexPath.row == (self.rowCount)) {
// add addButton
cell?.contentView.addSubview(self.addButton)
// scroll to last cell (that was just added)
// First figure out how many sections there are
let lastSectionIndex = self.fromToTableView.numberOfSections - 1
// Then grab the number of rows in the last section
let lastRowIndex = self.fromToTableView.numberOfRows(inSection: lastSectionIndex) - 1
// Now just construct the index path
let pathToLastRow = IndexPath(row: lastRowIndex, section: lastSectionIndex)
// Scroll to last cell
self.fromToTableView.scrollToRow(at: pathToLastRow as IndexPath, at: UITableViewScrollPosition.none, animated: true)
}
return cell!
}
You already figured out what the problem is :)
As you say here:
it seems that a cell is reused and the button is partly drawn
And that is exactly what happens here. You have a pool of UITableViewCell elements, or in your case SettingsCustomTableViewCell. So whenever you say:
let cell = fromToTableView.dequeueReusableCell(withIdentifier: "SettingsCell") as? SettingsCustomTableViewCell
Then, as the name implies, you dequeue a reusable cell from your UITableView.
That also means that whatever you may have set on the cell previously (like your button for instance) stays there whenever you reuse that specific instance of a cell.
You can fix this in (at least) three ways.
The Hack
In your tableView(_:cellForRowAt:indexPath:) you have this:
if(indexPath.row == (self.rowCount)) {
//last row, add plus button
}
You can add an else part and remove the plus button again:
if(indexPath.row == (self.rowCount)) {
//last row, add plus button
} else {
//not last row, remove button
}
The Right Way
UITableViewCell has the function prepareForReuse and as the documentation says:
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCell(withIdentifier:)
So, this is where you "reset" your custom table view cell. In your case you remove the plus button.
You already have a custom UITableViewCell it seems, so this should be a small change.
The Footer Way
As #paragon suggests in his comment, you can create a UIView and add that as a tableFooterView to your UITableView.
let footerView = YourFooterViewHere()
tableView.tableFooterView = footerView
Hope that helps

Dynamically add subViews to UITableViewCell

I need to add UILabels to UITableViewCell, but its dynamic, first cell can have 1 label, second can have 4 and I dont know before hand. So I tried this
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell: ReviewTableViewCell = reviewTableView.dequeueReusableCell(withIdentifier: "Review", for: indexPath) as! ReviewTableViewCell
var reviewObj:Review!
reviewObj = reviewArray[(indexPath as NSIndexPath).row]
let viewsAdded = commentViewsAddedDict[indexPath.row]
if(viewsAdded == nil)
{
for comment in reviewObj.commentArray
{
let label1 = UILabel()
label1.text = “text1”
label1.textColor = UIColor(hexString: "#333333")
let label2 = UILabel()
label2.text = “text2”
label2.numberOfLines = 0
label2.sizeToFit()
label2.textColor = UIColor(hexString: "#666666")
let label3 = UILabel()
label3.text = "----------------------------------------------------------------------"
label3.textColor = UIColor(hexString: "#eeeeee")
cell.stackView1.addArrangedSubview(label1)
cell.stackView1.addArrangedSubview(label2)
cell.stackView1.addArrangedSubview(label3)
}
commentViewsAddedDict[indexPath.row] = true
}
return cell
}
But what happens, the previously added views are not removed and it again tries to add new views.
So I want to know, what is the efficient way to do this.
Secondly, where I am going wrong.
Regards
Ranjit
You are using commentViewsAddedDict to figure out whether rows have been added or not. But whether these labels were added or not is not a function of the row in the table, but rather of the cell, which is reused.
So, I'd would advise:
eliminate this commentViewsAddedDict logic; and
move the logic regarding how many labels have been added to ReviewTableViewCell.
So, you might end up with:
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = reviewTableView.dequeueReusableCell(withIdentifier: "Review", for: indexPath) as! ReviewTableViewCell
var reviewObject = reviewArray[(indexPath as NSIndexPath).row]
cell.updateLabels(for: reviewObject)
return cell
}
And in ReviewTableViewCell:
func updateLabels(for reviewObject: ReviewObjectType) {
// add label if needed
// update label `text` if needed
// remove any labels that need to be removed
}
It's a little hard to be specific on the logic in updateLabels as the provided code snippet in the question is unclear, but the basic idea is that the ReviewTableViewCell should keep track of whether its labels have been added or not, and on the basis of the reviewObject, decide whether it needs to add a label, update an existing label, or remove any labels not needed for this particular reviewObject. But all of this "label state" logic is a function of the cell (which can be reused), not of which row in the table to which the cell corresponds.

tableview cells mix after scrolling (swift)

I made a table view that fills the cells with some data, which works fine.
The problem is that, when I scroll the table, all the cells get mixed, do you know a way to stop this?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let celda = tabla_reportes.dequeueReusableCellWithIdentifier("celda", forIndexPath: indexPath) as! celdaReportes
switch indexPath.section {
case 0:
celda.datos = tipos
celda.back = UIColor.lightGrayColor()
case 1:
celda.index = indexPath.row
celda.datos = fillArrayWithData(datos[indexPath.row] as! NSDictionary)
celda.back = UIColor.clearColor()
default:
break
}
celda.delegate = self
return celda
}
you should update field of the cell. maybe you here UITableViewCell reuse good practice can help you
Because the cells are reused for performance. Eg, when you scroll up the tableview and the number 1 cell disappears, the number 10 cell is actually your number 1 cell. Whatever you set in number 1 cell and the value is not updated stays with number 10 cell and so on...Try put a blank value inside your cell if it has no value.

tableViewCell image property reset when selected - swift

I do create a custom cell with 2 UIImageView inside and a UILabel this way:
let ChildCellID = "ChildCell"
if indexPath.section < 2 {
var cell = SectionCell.loadOrDequeueWithTableView(tableView, reuseIdentifier: SectionCellID)
if cell == nil {
cell = SectionCell.viewFromNib()
}
cell.delegate = self as SectionCellDelegate
cell.avatar?.loadAvatarURL(child.avatarUrl)
cell.avatar.layer.cornerRadius = cell.avatar.frame.size.width / 2
The attribut avatar is the UIImageView I decided to round. When a cell is selected my code goes:
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let tableViewCell = tableView.cellForRowAtIndexPath(indexPath)
if let cell : SectionCell = tableViewCell as? SectionCell {
cell.selection = !cell.selection
} else if let cell : ChildCell = tableViewCell as? ChildCell {
cell.selection = !cell.selection
}
return indexPath
}
Selection is the checkbox as UIImageView. My problem is when I select a cell, the cell.avatar loses his property meaning it goes back as a square form. The cell.avatar is still loaded and I tried cell.avatar.layer.cornerRadius = cell.avatar.frame.size.width / 2in the willSelectRowAtIndexPath and didSelectRowAtIndexPath but without success.
Am I missing anything that makes my avatar loses his rounded property ?
The only thing that could possibly be the origin of this problem would be the fact that I do use the checkbox UIImageView which is really near my avatar.
I found out that the checkbox near my avatar lacked a constraints that, for some random reason and even though it did not impact the avatar view, was resetting the avatar view rounded property.