1

I have made a table view in iOS that displays a list of buddy (friend) requests. For the buddy request cell, I have made it a prototype cell and have given it a custom class that extends from UITableViewCell. When I click the "Accept" button on the cell, I want to remove that row from the requests array I have and remove it from the table view as well.

The three options I have considered are

1) Giving the custom cell a property for row that corresponds to the row in the table, and hence, the row in the requests array. Then, when accept is called, pass that row to the delegate function and call

requests.removeAtIndex(row) 
tableView.reloadData() 

which updates all the custom cells' row property. This method works. However, is this a bad practice to reload the table data (it's only reloading from the stored array, not making a network request)

2) Giving the custom cell the row property, but then calling

self.requests.removeAtIndex(row)
self.requestsTableView.beginUpdates()
self.requestsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow:row, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.requestsTableView.endUpdates()

However, this does not update the row value in each of the cells following the deleted cell, and I would somehow either have to update them all, or call reloadData() which isn't what I want to do.

3) Instead of passing the row value, when the "Accept" button is clicked, search for the username in the buddies list, get the index of where it is found, and then delete the row in the table using that index and deleteRowsAtIndexPaths. This seems okay to do, especially since I'll never have a huge amount of buddy requests at once and searching won't require much time at all, but I figure if I had immediate access to the row value, it would make things cleaner.

Here is the code:

View Controller

class RequestsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, RequestTableViewCellDelegate
{

    // Outlet to our table view
    @IBOutlet weak var requestsTableView: UITableView!
    let buddyRequestCellIdentifier: String = "buddyRequestCell"

    // List of buddies who have sent us friend requests
    var requests = [Buddy]()

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(animated: Bool) {
        self.getBuddyRequests()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


    // MARK: -Table View

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return requests.count
    }


    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell: RequestTableViewCell = tableView.dequeueReusableCellWithIdentifier(buddyRequestCellIdentifier) as! RequestTableViewCell

        let buddy = requests[indexPath.row]
        let fullName = "\(buddy.firstName) \(buddy.lastName)"
        cell.titleLabel?.text = fullName
        cell.buddyUsername = buddy.username
        cell.row = indexPath.row
        cell.delegate = self

        return cell
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let buddy = self.requests[indexPath.row]
    }

    func didAccpetBuddyRequest(row: Int) {
        // Remove buddy at the 'row' index

        // idea 1: update all cells' 'row' value
        //self.requests.removeAtIndex(row)
        // reloading data will reload all the cells so they will all get a new row number
        //self.requestsTableView.reloadData()

        // idea 2
        // Using row doesn't work here becuase these values don't get changed when other cells are added/deleted
        self.requests.removeAtIndex(row)
        self.requestsTableView.beginUpdates()
      self.requestsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow:row, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
        self.requestsTableView.endUpdates()

        // idea 3: don't use row, but search for the index by looking for the username
    }

    // MARK: -API

    func getBuddyRequests() {
        // self.requests = array of buddy requests from API request
        self.requestsTableView.reloadData()

    }
}

Custom UITableViewCell and protocol for the delegate call

protocol RequestTableViewCellDelegate {
    func didAccpetBuddyRequest(row: Int)
}

class RequestTableViewCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var acceptButton: UIButton!

    var delegate: RequestTableViewCellDelegate?
    var buddyUsername: String?
    var row: Int?

    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
    }

    @IBAction func touchAccept(sender: AnyObject) {
        // <code goes here to make API request to accept the buddy request>
        self.delegate?.didAccpetBuddyRequest(self.row!)

    }

}

Thanks for taking the time to read this, I appreciate any help/best practices that you know that could help me in this situation.

hoffware
  • 205
  • 6
  • 19
  • 1
    See my answer here http://stackoverflow.com/a/22827645/790842. This is the best solution for your problem. – iphonic May 04 '15 at 05:19

2 Answers2

0

There shouldn't be a problem with giving the cell the indexPath and delegate properties, and then informing the delegate when the Accept button has been tapped. You do need to call reloadData(), though, to update the references in the cells that are affected.

If you wish to minimise the number of reloaded rows, call reloadRowsAtIndexPaths() instead, but I think that creating the loop that creates the NSIndexPath objects will slow your app down just the same.

Matthew Quiros
  • 13,385
  • 12
  • 87
  • 132
  • Is that any different than updating the requests array and then calling reloadData()? I guess the deleteRowsAtIndexPaths allows there to be an animation, which is a plus. – hoffware May 04 '15 at 05:07
  • Edited my answer--you don't need to call `deleteRowsAtIndexPaths` when you call `reloadData`. I was looking at some old code that initially implemeted begin- and endUpdates. – Matthew Quiros May 04 '15 at 05:15
  • Would it be more efficient/faster to reload the table, or to search through the requests list. Is reloading the table a negligible amount of time, and are there any negative effects of reloading the table? – hoffware May 04 '15 at 21:12
  • Negative effects: no animation, and if you're doing concurrent deletions, you will soon get a crash because you're deleting the wrong or non-existent index path, because some other deletion task already completed and did a `reloadData` and the index paths have been changed. For such tasks, an array + dictionary hybrid is a better data source (google `M13OrderedDictionary`). But as for efficiency, you shouldn't have to worry--`reloadData` is efficient because while it changes the data source, it only refreshes the visible table view cells. – Matthew Quiros May 05 '15 at 07:09
0

As an alternative I can suggest you another way:

First add action method to your acceptButton in viewController. Inside that method you can get indexPath of the cell that contains button. Here is implementation

@IBAction func acceptDidTap(sender: UIButton) {
    let point = tableView.convertPoint(CGPoint.zeroPoint, fromView: button)
    if let indexPath = tableView.indexPathForRowAtPoint(point) {
        // here you got which cell's acceptButton triggered the action
    }
}
mustafa
  • 15,254
  • 10
  • 48
  • 57