So I have a TableView with custom cells that I make from fetching data from a server. I have a variable 'selectedIndex' which I use to keep track and add a checkmark accessory to my cell. Weirdly, it only works after I scroll the selected cell (cell with indexPath.row is equal to off the screen and back. Here is the code in my willDisplayCell method:
if selectedIndex == indexPath.row {
if let accessory = cell.viewWithTag(528) as? UITableViewCell {
accessory.frame = (cell.checkmarkView.bounds.offsetBy(dx: 0, dy: 7))
accessory.accessoryType = .checkmark
accessory.isUserInteractionEnabled = false
accessory.backgroundColor = UIColor.clear
accessory.tag = 528
accessory.isHidden = false
print("accessory not nil")
} else {
let accessory = UITableViewCell()
accessory.frame = (cell.checkmarkView.bounds.offsetBy(dx: 0, dy: 7))
accessory.accessoryType = .checkmark
accessory.isUserInteractionEnabled = false
accessory.backgroundColor = UIColor.clear
accessory.tag = 528
accessory.isHidden = false
cell.addSubview(accessory)
print("accessory nil")
}
} else {
let accessory = cell.viewWithTag(528)
accessory?.isHidden = true
}
For example, when the selected index is 0, the checkmark is not displayed at first view, and the logs print ("accessory nil"). When I scroll the cell at index 0 off-screen, and scroll to it again, the checkmark is now displayed and the logs print ("accessory not nil"). For more information, tapping on a cell works just as expected.
Edit:
I know I'm adding another TableViewCell to my cell! I'm only doing that because I need my checkmark accessory to be in a different position (left-aligned, top-aligned) than the default one (right-aligned, vertically centered). So what I did was add a view in my XIB that is in the desired position (right-aligned, top-aligned) and and aligned my programatically-made cell to it. If you could show me another approach it would be appreciated.
Removed cell.addSubview from the first if. Still behaves the same.
Thanks!
You are adding UITableViewCell into your dequeued original cell, this is unnecessary, you need implement this logic in your cellForRowAt datasource method,
updated
If you need a custom position for your accessory view use cell.layoutMargins check my updated code
try this instead
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier", for: indexPath) as! YourCellClass
cell.accessoryType = .none
if selectedIndex == indexPath.row {
cell.accessoryType = .checkmark
}
//if you need a custom position for your accessory view
cell.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 100)
}
UITableView will optimize your cells by reusing them. So in your case it seems better to use tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) instead of calling the Cell's constructor yourself or find the cell back by looking for it's tag.
Besides that the part which determines the state of the cell should be in the UITableView's tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell function instead of the willDisplayCell. This might solve your problem and prevents issues with reusing cells.
Related
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.
I am currently working on a swift based HRM project. where it requires to show a tableview with slightly customized cell. cells it self containing two buttons, under some business logic one button would be hidden. for example ,
if the current user is the employee himself , he can see a list, the cell containing his name can see two buttons,but other cell would show simply one button.
i have tried the followings:
1. if the userId == employeeId (employeeId came from a model) then ,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimTableViewCell", for: indexPath) as! ClaimTableViewCell
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}
also , i have tried
if(self.claimdata[indexPath.section].employeeId != self.empId) {
cell.CancelButton.frame.size.height = 0
}
works fine for the first frame , the problem begins when i begin to scroll. for some unintended cell it also shows two buttons.
Am I missing something?
This issue is due to the cell reusability in UITableView.
Use below code in your cellForRowAtIndexPath method.
cell.CancelButton.isHidden = true
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}
As tableView cell is reusableCell
dequeueReusableCell withIdentifier
you just need to give else condition so when it reuse the cell again it knowns what to do with CancelButton.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimTableViewCell", for: indexPath) as! ClaimTableViewCell
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}else{
cell.CancelButton.isHidden = true
}
}
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
I'm struggling to load a cell in cellForRowAtIndexPath in such way so that I can give a custom offset to one cell only - in my case only to Cell 1, Section 1. I'd like to give a custom offset to the cell, as the same cell (xib) is used in other parts of the project where it extends to the edges of the table without any offset. All my cells are designed as *.xib files. I'm willing to set something in awakeFromNib() or other methods, if there's the need for that.
Is there any way to set the cell offset, inset, margins, padding (not sure about the correct wording) at time of the creation or at time of loading of the cell?
I've tried to set margins in this way, but it doesn't work:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.section == 0) && (indexPath.row) == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCell
cell.layoutMargins = UIEdgeInsetsMake(10, 10, 10, 10)
return cell
}
} else {
// Load other cells
}
I think you're close with your cellForRowAtIndexPath implementation. Try applying the margins to the cell's contentView (the superview for your cell's content, and a subview of the cell). You may also have to set the cell's background colour to clear (the contentView will still be white).
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.section == 0) && (indexPath.row) == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCell
cell.contentView.layoutMargins = UIEdgeInsetsMake(10, 10, 10, 10)
return cell
}
} else {
// Load other cells
}
No article explains it clearly regarding my query, I have three cells in a static table and I want to hide second cell when users taps on first cell. Any kind of help is appreciated.
Although you cannot stop the static table from trying to show your cells, you can set their height to zero, making them effectively invisible:
Add this method to your table view controller delegate class:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)
return cell == myHiddenCell ? 0 : super.tableView(tableView, heightForRowAtIndexPath:indexPath)
}
In the didSelectCellAtIndexPath method, you can set the height to 0 to hide it :
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
let indexPath = NSIndexPath(forItem: 1, inSection : 0)
let secondCell = tableview.cellForRowAtIndexPath(indexPath)
secondCell.frame.size.height = 0;
self.view.layoutSubviews()
}
}
If you want an animation, just put self.view.layoutSubviews() in an UIView animation method UIView.animateWithDuration... etc
For me, setting the height to 0 for some cells and another height for other cells wasn't an option, as all my cells have different height.
I created another cell in Storyboard, and set row height of 0 (in size inspector). Then in the code, I show the cell with height = 0 if I want to hide it, if not, I show the other cell:
if (hideCell) {
let hiddenCell = tableView.dequeueReusableCell(withIdentifier: "hiddenCell",for: indexPath) as! TheWallTableViewCell
return hiddenCell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath) as! TheWallTableViewCell
return cell
}