Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView section footer moved after inserting new table rows

I have a UITableView that displays a list of items. The table view controller has an array of items that gets updated asynchronously upon response from a call to a web service. Here is an example of what I have (in Swift):

class MyTableViewController : UITableViewController {
    var items: [ItemClass] = []

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("RootCell", forIndexPath: indexPath) as UITableViewCell
        if indexPath.section == 0 {
            let item = items[indexPath.row]
            cell.textLabel!.text = item.name
        }
        else if indexPath.section == 1 {
            // Another section not shown here
        }
        return cell
    }
}

I want each section of this table to have a footer with a button in it, so I also include this:

    override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let button = UIButton.buttonWithType(.System) as UIButton
        button.setTitle("Add", forState:UIControlState.Normal)
        if section == 0 {
            button.addTarget(self, action:Selector("itemAddPressed:"), forControlEvents:UIControlEvents.TouchUpInside)
        }
        else if section == 1 {
            // other section not shown here
        }
        return button
    }

Items are added to the items array via an callback that gets invoked outside of the main UI thread. It looks something like this:

private func itemWasAdded(item: ItemClass) {
    dispatch_async(dispatch_get_main_queue()) {
        self.items += [item]
        self.tableView!.reloadData()
    }
}

This all works fine, but my use of the table's reloadData seems like overkill to me when I know that only one item is being added at a time. So, I tried to update it to do the following:

private func itemWasAdded(item: ItemClass) {
    dispatch_async(dispatch_get_main_queue()) {
        self.items += [item]
        let indexPath = NSIndexPath(forRow:self.item.count - 1, inSection:0)
        self.tableView!.insertRowsAtIndexPaths([indexPath], withRowAnimation: .None)
    }
}

When I do it this way, the table continues to work but there's a problem with the footer buttons. Instead of showing the Add button I've created in each section's footer, I see the add button for section 0 showing at the bottom of the table view, underneath section 1.

It seems like doing something to force a refresh of the table seems to fix the problem. This UITableViewController is the top controller in a UINavigationController, and if I select a table cell a new view controller is pushed onto the navigation controller. Navigating back to the original table view controller, the footer buttons are displayed in the correct place.

The easiest way to make this work is just to use reloadData instead of insertRowsAtIndexPaths. But I'd like to know what I'm doing wrong here so that I can avoid reloading all table data if possible.

Am I using the insertRowsAtIndexPaths incorrectly here?

like image 635
Tim Dean Avatar asked Jan 06 '15 02:01

Tim Dean


1 Answers

I thought this is happening because beginUpdates() and endUpdates() were missing and it would be very simple mistake. But I had exactly a same problem when I tested it.

I will just share my observations.

When I try with tableview style Grouped, the problem above is not happening. But if I use Plain style, the footer goes down to the bottom of the tableview. I guess there's something to do with different footer view behaviors depending on its style and the way table view layout its content after updating its data.

If you have to use tableview with Plain style, you have to handle the case where the sum of its contents' heights( cells and section footer views) is less than the tableview height right before inserting a row. Something like,

let contentHeight = CGFloat(items.count * cellHeight + numberOfSection*footerHeight)
if contentHeight < tableViewHeight {
   tableView.frame = CGRectMake(0, 0, view.frame.size.width, numberOfSection*CGFloat(items.count * cellHeight + footerHeight))    
} else {
   tableView.frame = viewHeight 
}

In order to make everything clean, you should understand what are the behaviors of section footer/header of tableview with its different style and frames. Hope that you can find the better solution that meets your requirements.

like image 188
HMHero Avatar answered Nov 15 '22 03:11

HMHero