Reusing parts of Table View Cell in different controllers - ios

I have a table view cell that has a button which, when clicked, shows an action view.
After reading this answer saying that the logic to show the action view should be handled in the controller.
The problem is that I use these table view cells in several different controllers and it seems counterintuitive to copy and paste the action view logic into each controller where the table view cells exist, especially considering the maintenance required.
So my question is, what is the best approach to handling this? Is there a way that I can handle all of the action view logic in a single place, and refer the controllers to that code?

Have the action for click in tableview cell and pass a variable in cellforrow so you know from which screen you are coming and accordingly handle the click.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : RiderCell! = tableView.dequeueReusableCell( withIdentifier: "RiderCell") as! RiderCell
cell.UpdateCell(from:"Profile")
cell.selectionStyle = .none
return cell as RiderCell
}
in you table cell
import UIKit
class RiderCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func UpdateCell(from:String){
//manage click here
}
}

Related

indexPath is nil when passing cell from delegate to UITableViewController

I have a button called addSet at the end of each section of my tableView, it is used as a footerView and it is supposed to tell the UITableViewController of when it is pressed and in which section. My code for the custom table view cell is as follows
import UIKit
class FooterTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
var footerDelegate:FooterTableViewCellDelegate?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func addSetIsPressed(_ sender: AnyObject) {
print("Add Set is pressed")
footerDelegate?.didAddSetIsPressed(cell:self)
}
}
protocol FooterTableViewCellDelegate {
func didAddSetIsPressed(cell:FooterTableViewCell)
}
And in my TableViewController, I implement it like so
func didAddSetIsPressed(cell: FooterTableViewCell) {
let indexPath = tableView.indexPath(for: cell)
print("Index path is \(indexPath)")
}
I want to get the indexPath (the section specifically) when the user taps my button, however it always returns nil. What am I doing wrong?
To put things in context. I am using this cell as a footerView, so the cell is implemented like so
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "footerCell") as! FooterTableViewCell
cell.footerDelegate = self
return cell
}
so it isn't implemented in cellForRow at indexPath like it would normally be
Thanks in advance.
The thing is you put the cell FooterTableViewCell as a viewForFooterInSection,
so it's not used as a UITableViewCell in the UITableView, so the UITableView is not holding the indexPath of this UITableViewCell "Cause i said previously, the cell's view only is used as a footerView"
You need to add the button inside the cell that's being rendered on the UITableView. "The one that's being returned in the tableView(_:cellForRowAt:) method"
On a side note i noticed that you have a variable named footerDelegate in your cell, it needs to be weak to avoid memory leaks as you assign your TableViewController as this delegate,
so the UITableViewCell holds a strong reference of the TableViewController that leads to memory leak cause also in the view hierarchy the TableViewController contains the UITableView as a subView.
I found out how to do it, in order to detect the section in which the button was tapped. There must be an outlet reference in the FooterCell and in the tableViewController, in viewForFooter in Section, just add the following line
cell.addSetOutlet.tag = section

how to select multiple cells using button and image to check and uncheck

Actually I am trying to select and deselect multiple rows in tableview using image in tableviewcell,and also I want to delete selected rows when I click on delete button which is outside of the tableview.Here I am able to delete the selected row and am able to select and deselect single row.But I want to select and deselect multiple rows to delete when the rows are selected.Can anyone help me to do this.Thanks in advance.
//In tableviewcell class
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected{
checkOrUncheckImg.image = UIImage(named:"check")
}else{
checkOrUncheckImg.image = UIImage(named:"uncheck")
}
// Configure the view for the selected state
}
Create a dictionary or a set of the IDs or indexPath of the cells that are selected. I'm going to use IDs as they are more unique, but it really depends on your DB. If your objects don't have a unique identifier use indexPath
var arrayIDs = Set<String>()
Implement didSelectRowAtIndexPath tableView delegate method. When the user taps the cell, add or remove the ID to the arrayIDs
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let objectID = objects[indexPath.row].id
if (arrayIDs.contains(objectID)){
arrayIDs.remove(objectID)
}else{
arrayIDs.insert(objectID)
}
}
In your cellForRowAtIndexPath, if the arrayIDs contains the objects id, set selected image
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellClass") as? YourCellClass {
if (arrayIDs.contains(objectID){
cell.checkOrUncheckImg.image = UIImage(named:"check")
}else{
cell.checkOrUncheckImg.image = UIImage(named:"uncheck")
}
return cell
}
And when clicking the button outside of the cell
#IBAction func buttonPressed(_ sender: Any) {
//Do something with arrayIDs (loop through them and delete each one with REST call and from datasource or whatever you're doing, then reloadData to update table
tableView.reloadData()
}
I didn't test any of this, so there may be some small syntax errors, but you get the gist.

How to stop UITableViewCell flashing to white when deselecting?

I have the common pattern of a UITableView with a secondary view controller which gets pushed over the top when a row is selected. To give the user some context when they dismiss the second view controller and return to the tableview, that first view controller has this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let index = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: index, animated: animated)
}
}
That results in this unintended and jarring transition in which the cell being deselected fades it's background away, before snapping back to normal :
My expectation was that it would transition from the partially subdued state selection left it in directly back to the normal, dark state.
(The cell is very much a work-in-progress - it's far from finished)
Following the suggestions here isn't really an option as I do want to preserve the context hint and the cell as a whole should continue to have a white background.
In response to Rico's question, the cell is created as a .swift and .xib pair, the hierarchy of views being:
The Swift does very little - sets .textInsets on the two labels, draws the disclosure indicator in the button.
I believe this is because the default implementation of setSelected removes the background color of all subviews of the cell. What you can do is override setSelected and/or setHighlighted and update the cell yourself.
This also allows you to create a custom look for selected cells.
Example that uses a red background when selected, and white when not selected:
override func setSelected(_ selected: Bool, animated: Bool) {
let animationDuration = animated ? 0.3 : 0
UIView.animate(withDuration: animationDuration) {
self.backgroundColor = highlighted ? UIColor.red : UIColor.white
}
}
Instead of deselecting the row in viewWillAppear(_:), call deselectRow(at:animated:) inside the tableView(_:didSelectRowAt:) method, i.e
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
//Your rest of the code...
}
Edit-1:
In viewWillAppear(_:), you need to deselect the cell in UIView.animate(withDuration:animations:) with animation set to true in deselectRow(at:animated:), i.e.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIView.animate(withDuration: 0.2) {
if let indexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
}

How do I pass button action from a nested collectionView cell?

I have a MainCollectionView used for scrolling between items, inside of one of these cells I have another collectionView with cells. In that collection view, I have a button for each cell. My question is how do I pass action from my button to my MainCollectionView when it is tapped? I did create protocol for that button in the cell but I don't know how to let MainCollectionView know when my button is tapped. I can call action from my cell class but I think it is better to run it in Model which is my MainCollectionView. Below is my button protocol.
protocol ThanhCaHotTracksCellDelegate: class {
func handleSeeAllPressed()}
weak var delegate: ThanhCaHotTracksCellDelegate?
#objc func handleSeeAllButton(){
delegate?.handleSeeAllPressed()
}
LIke NSAdi said, you're on the right track, but the delegate pattern is a bit much overhead for just a single task like notifying about a button press.
I prefer using closures, because they're lightweight and helps to keep related code together.
Using Closures
This is what I'm always doing in UITableView. So this will work in UICollectionView too.
class MyTableViewCell: UITableViewCell {
var myButtonTapAction: ((MyTableViewCell) -> Void)?
#IBAction func myButtonTapped(_ sender: Any) {
myButtonTapAction?(self)
}
}
So when I dequeue my cell and cast it to MyTableViewCell I can set a custom action like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCellReuseIdentifier", for: indexPath) as! MyTableViewCell
cell.myButtonTapAction = { cell in
// Put your button action here
// With cell you have a strong reference to your cell, in case you need it
}
}
Using direct reference
When you're dequeueing your UICollectionView cell you can obtain a reference to your button by casting the cell to your cell's custom subclass.
Then just do the following
cell.button.addTarget(self, action: #selector(didTapButton(_:)), forControlEvents: .TouchUpInside)
And outside have a function:
#objc func didTapButton(_ sender: UIButton) {
// Handle button tap
}
Downside of this is that you have no direct access to your cell. You could use button.superview? but it's not a good idea since your view hierarchy could change...
You're on the right track.
Make sure MainCollectionView (or the class that contains) it implements ThanhCaHotTracksCellDelegate protocol.
Then assign the delegate as self.
Something like...
class ViewController: ThanhCaHotTracksCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
subCollectionView.delegate = self
}
}

didSelectRowAtIndexPath with a tableView that has sections

I have a tableView with several section and each section contains only one cell (this is done to create a gab between each cell). Im trying to create a custom selection view for my cell when it is selected.
when I select a row, the custom selection view is being added to more than one cell. I know the problem is because cells are being reused. What is the best suitable solution to overcome this problem?
This is my code.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let myCell = tableView.cellForRowAtIndexPath(indexPath) as? PredefinedServicesCell{
let selectionView = UIView()
selectionView.backgroundColor = UIColor(hex: 0x3399CC).colorWithAlphaComponent(0.2)
selectionView.layer.cornerRadius = (myCell.containerView.layer.cornerRadius)
selectionView.frame = (myCell.containerView.frame)
myCell.containerView.addSubview(selectionView)
}
}
You can make you custom selected view hide or show in the following method in your CustomCell Class
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.customSelectedView.hidden = !selected
}

Resources