I am trying to remove a cell from my collectionView, however. When I remove the object from my datasource and attempt a batchupdate, it says the cell doesn't exist anymore.:
'NSInternalInconsistencyException', reason: 'attempt to delete item 0 from section 0 which only contains 0 items before the update'
Before this code I remove the content from my core data, and the line [[usermanager getSelectedUser]loadCards]; actually reloads the datasource containing the content for the cells by getting them from the Core Data.
- (void)cardRemoved:(NSNotification *)note {
NSDictionary *args = [note userInfo];
Card *card = [args objectForKey:#"card"];
[[usermanager getSelectedUser]loadCards];
[self.collectionView performBatchUpdates:^{
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:card.position.intValue inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished){
[self.collectionView setDelegate:self];
[self.collectionView setDataSource:self];
[self.collectionView reloadData];
}];
}
If I print out the amount of Cells before I call the loadCards line, I get the correct amount of rows(As expected).
EDIT
This is what loadCards calls:
-(NSMutableArray *)getCards{
UserModel *selectedUser = [self getSelectedUserFromDB];
NSMutableArray *cards = [[NSMutableArray alloc] init];
for(CardModel *cardModel in selectedUser.cards){
[cards addObject:[self modelToCard:cardModel]];
}
return cards;
}
I noticed, even if I don't call the loadCards method, It says there are no items in the view.
Can anyone help me out? Thank you
Remove the cells from the UICollectionView, then remove them from the model. The same thing applies to UITableView. You also don't need to reload the collection view after removing items.
If you prefer you can just remove items from the model and then reload the collection view and the items that aren't in the model will disappear from the collection view, but without the same animation that comes from removing items from a collection view.
Related
Tearing my hair out trying to get this to work. I want to perform [self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];,
More detailed code of how I delete:
int index = (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
This works fine however if I get a push notification (i.e a new message received) whilst deleting the app will crash and display an error like:
Assertion failure in -[UITableView _endCellAnimationsWithContext:],
/SourceCache/UIKit/UIKit-3347.44/UITableView.m:1327
2015-07-04 19:12:48.623 myapp[319:24083] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'attempt to delete row 1 from section 0 which only contains 1 rows
before the update'
I suspect this is because my data source is changing, the size of the array that
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
references while deleting won't be consistent because it was incremented by one when the push notification triggered a refresh. Is there any way I can work around this? Am I correct that deleteRowsAtIndexPaths uses the numberOfRowsInSection method?
So, in order to solve your problem you need to ensure that your data source will not change while some table view animations are in place. I would propose to do the following.
First, create two arrays: messagesToDelete and messagesToInsert. These will hold information about which messages you want to delete/insert.
Second, add a Boolean property updatingTable to your table view data source.
Third, add the following functions:
-(void)updateTableIfPossible {
if (!updatingTable) {
updatingTable = [self updateTableViewWithNewUpdates];
}
}
-(BOOL)updateTableViewWithNewUpdates {
if ((messagesToDelete.count == 0)&&(messagesToInsert.count==0)) {
return false;
}
NSMutableArray *indexPathsForMessagesThatNeedDelete = [[NSMutableArray alloc] init];
NSMutableArray *indexPathsForMessagesThatNeedInsert = [[NSMutableArray alloc] init];
// for deletion you need to use original messages to ensure
// that you get correct index paths if there are multiple rows to delete
NSMutableArray *oldMessages = [self.messages copy];
for (id message in messagesToDelete) {
int index = (int)[self.oldMessages indexOfObject:message];
[self.messages removeObject:message];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[indexPathsForMessagesThatNeedDelete addObject:indexPath];
}
for (id message in messagesToInsert) {
[self.messages insertObject:message atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[indexPathsForMessagesThatNeedInsert addObject:indexPath];
}
[messagesToDelete removeAllObjects];
[messagesToInsert removeAllObjects];
// at this point your messages array contains
// all messages which should be displayed at CURRENT time
// now do the following
[CATransaction begin];
[CATransaction setCompletionBlock:^{
updatingTable = NO;
[self updateTableIfPossible];
}];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:indexPathsForMessagesThatNeedDelete withRowAnimation:UITableViewRowAnimationLeft];
[tableView insertRowsAtIndexPaths:indexPathsForMessagesThatNeedInsert withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
[CATransaction commit];
return true;
}
Lastly, you need to have the following code in all functions which want to add/delete rows.
To add message
[self.messagesToInsert addObject:message];
[self updateTableIfPossible];
To delete message
[self.messagesToDelete addObject:message];
[self updateTableIfPossible];
What this code does is ensures stability of your data source. Whenever there is a change you add the messages that need to be inserted/deleted into arrays (messagesToDelete and messagesToDelete). You then call a function updateTableIfPossible which will update the table view's data source (and will animate the change) provided that there is no current animation in progress. If there is an animation in progress it will do nothing at this stage.
However, because we have added a completion
[CATransaction setCompletionBlock:^{
updatingTable = NO;
[self updateTableIfPossible];
}];
at the end of the animations our data source will check if there are any new changes that need to be applied to the table view and, if so, it will update the animation.
This is a much safer way of updating your data source. Please let me know if it works for you.
Delete a Row
int index = (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
Delete a Section
Note : if your TableView have multiple section than you have to delete whole section when section contain only one row instead of deleting row
int index = (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];
NSIndexPath *indexPath = [NSIndexSet indexSetWithIndex:0];
[tableView beginUpdates];
[tableView deleteSections:#[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
I have a few test about your code above. And you cannot do that at the same time the least we can do is something like:
//This will wait for `deleteRowsAtIndexPaths:indexes` before the `setCompletionBlock `
//
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[tableView reloadData];
}];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
[CATransaction commit];
In your code whe have:
/*
int index = (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
*/
This is not safe because if if the notification is triggered and for some reason [self.tableView reloadData]; called right before deleteRowsAtIndexPaths that will cause an crash, because the tableView is currently updating the datas then interrupt by the deleteRowsAtIndexPaths:, try this sequence to check:
/*
...
[self.tableView reloadData];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
This will cause a crash...
*/
Hmm.. Back to your code, Let's have a simulation that could cause the crash.. THIS is just an assumption though so this is not 100% sure (99%) only. :)
Let assume self.messageToDelete is equal to nil;
int index = (int)[self.messages indexOfObject: nil];
// since you casted this to (int) with would be 0, so `index = 0`
[self.messages removeObject: nil];
// self.messages will remove nil object which is non existing therefore
// self.messages is not updated/altered/changed
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
// indexPath.row == 0
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
// then you attempted to delete index indexPath at row 0 in section 0
// but the datasource is not updated meaning this will crash..
//
// Same thing will happen if `self.messageToDelete` is not existing in your `self.messages `
My suggestion is check the self.messageToDelete first:
if ([self.messages containsObject:self.messageToDelete])
{
// your delete code...
}
Hope this is helpful, Cheers! ;)
'attempt to delete row 1 from section 0 which only contains 1 rows before the update'
This says your index path, during deletion, references row 1. However row 1 doesn't exist. Only row 0 exists (like the section, they are zero-based).
So how are you getting an indexPath one greater than the number of elements?
Can we see your notification handler that inserts a row? You could do some hack where if the animation is in process, you performSelector:withDelay: during the notification processing to allow time for the animation to complete.
In you code, the reason for crash is : you are updating array but not update data source of UITableView. so your dataSource also needs to reflect the changes by the time endUpdates is called.
So as per Apple Documentation.
ManageInsertDeleteRow
To animate a batch insertion, deletion, and reloading of rows and sections, call the corresponding methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion, deletion, and reloading methods within this block, row and section indexes may be invalid. Calls to beginUpdates and endUpdates can be nested; all indexes are treated as if there were only the outer update block.
At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.
Example:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
Hope this help you.
I think the answer is alluded to in many of the comments here, but I'll try and tease it out a bit.
The first problem is that it doesn't look like you are bracketing your deletion method call in the beginUpdates/endUpdates methods. This is the first thing I would fix, otherwise as warned in Apple's documentation things can go wrong. (That doesn't mean they will go wrong, you're just safer doing it this way.)
Once you've done that, the important thing is that calling endUpdates checks the data source to make sure that any insertions or deletions are accounted for by calling numberOfRows. For example, if you delete a row (as you are doing), when you call endUpdates you best be sure that you've also deleted an item from the data source. It looks like you are doing that part right because you are removing an item from self.messages, which I assume is your data source.
(Incidentally you are correct that calling deleteRows... on its own without bracketing in beginUpdates/endUpdates also calls numberOfRows on the data source, which you can easily check by putting a break point in it. But again, don't do this, always use beginUpdates/endUpdates.)
Now it is possible that between calling deleteRows... and endUpdates someone else is modifying the self.messages array by adding an object to it, but I don't know if I really believe that because it would have to be extremely unfortunately timed. It's far more likely that when you receive a push message you aren't handling the insertion of the row properly, again by not using beginUpdates/endUpdates, or by doing something else wrong. It would be helpful if you could post the part of the code that handles the insertion.
If that part looks OK then it does look like you are very unfortunately making changes to the self.messages array on a different thread while the deletion code is being called. You can check this by adding a log line where your insertion code adds a message to the array:
NSLog(#"Running on %# thread", [NSThread currentThread]);
or just putting a break point in it and seeing which thread you end up on. If that is indeed the problem, you can dispatch the code that modifies the array and inserts the row onto the main thread (where it should be anyway since it's a UI operation) by doing something like below:
dispatch_async(dispatch_get_main_queue(), ^{
[self.messages addObject:newMessage];
[self.tableView beginUpdates];
[self.tableView insertRows...]
[self.tableView endUpdates];
});
That will ensure that the messages array won't get mutated by another thread while the main thread is busy deleting the row.
In my iOS app I am using Core Data to show data on a table view this is working fine .
what i want to do is :
when clicking on the navigation back button of view controller i want
to delete tableview cell if the textview on the View controller is empty .
i want to do it like Apple note app .
can any one hellp me with sample code please
here is my code to check if textview is empty or not
if( self.info .text.length <= 0 ){
}
1.Make sure your DataSource is something like this a
NSMutableArray *dataSource = [NSMutableArray alloc]initWitObjects:#"One",#"Two",#"Three",#"Four",#"Five",nil];
2.Remove wanted objects from your dataSource like so
[dataSource removeObjectAtIndex:2];
Delete rows in UITableView with animation
Create indexPaths to delete like this
NSArray *deleteIndexPaths = [[NSArray alloc] initWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
nil];
Finally delete them from the tableView like this
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
Or you can simply call
[self.tableView reloadData];
the doc about reloadData of UITableView says(http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html): "it should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates".
My question is, why? (especially first part, italic).
There's a book which I am reading which implements adding items to the UITableView like this:
// Add new item to the table
- (IBAction)addNewItem:(id)sender
{
// Update the model, add a new item
BNRItem *newItem = [[BNRItemStore sharedStore] createItem];
// Figure out where that item is in the array
int lastRow = [[[BNRItemStore sharedStore] allItems] indexOfObject:newItem];
// Create the corresponding index path
NSIndexPath *ip = [NSIndexPath indexPathForRow:lastRow inSection:0];
// Insert this new row into the table
[[self tableView] insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
withRowAnimation:UITableViewRowAnimationTop];
}
whereas same could be achieved also like this:
// Add new item to the table
- (IBAction)addNewItem:(id)sender
{
// Update the model, add a new item
[[BNRItemStore sharedStore] createItem];
[[self tableView] reloadData];
}
By reload data you're forcing all rows to be recreated so when you have information what is new it is just much more performant to tell table view what have to be added/removed, especially when, for example, you added one row at the end and that part is currently not on screen (so view does not have to be re-rendered, only scroll high have to be recalculated).
I have a UICollectionView and it works fine, but I want to add a few UICollectionViewCells items programmatically into the collection view.
So how can I achieve this?
To further clarify: when I say programmatically I mean inserting a cell during runtime, when an action is fired, not when the app is loaded (using the viewDidLoad method). I know when the model is updated and the call is made to UICollectionView in the insertItemsAtIndexPaths: method. It should create a new cells, but it's not doing that, it's throwing an error.
...By referring to UICollectionView documentation
You can accomplish:
Inserting, Deleting, and Moving Sections and Items To insert, delete,
or move a single section or item, follow these steps:
Update the data in your data source object.
Call the appropriate method of the collection view to insert or delete the section or item.
It is critical that you update your data source before notifying the
collection view of any changes. The collection view methods assume
that your data source contains the currently correct data. If it does
not, the collection view might receive the wrong set of items from
your data source or ask for items that are not there and crash your
app. When you add, delete, or move a single item programmatically, the
collection view’s methods automatically create animations to reflect
the changes. If you want to animate multiple changes together, though,
you must perform all insert, delete, or move calls inside a block and
pass that block to the performBatchUpdates:completion: method. The
batch update process then animates all of your changes at the same
time and you can freely mix calls to insert, delete, or move items
within the same block.
From your Question: you can for example register A gesture Recognizer, and Insert a NEW cell by
doing the following:
in
// in .h
#property (nonatomic, strong) NSMutableArray *data;
// in .m
#synthesize data
//
- (void)ViewDidLoad{
//....
myCollectonView.dataSource = self;
myCollectionView.delegate = self;
data = [[NSMutableArray alloc] initWithObjects:#"0",#"1", #"2" #"3", #"4",
#"5",#"6", #"7", #"8", #"9",
#"10", #"11", #"12", #"13",
#"14", #"15", nil];
UISwipeGestureRecognizer *swipeDown =
[[UISwipeGestureRecognizer alloc]
initWithTarget:self action:#selector(addNewCell:)];
swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
[self.view addGestureRecognizer:swipeDown];
//..
}
-(void)addNewCell:(UISwipeGestureRecognizer *)downGesture {
NSArray *newData = [[NSArray alloc] initWithObjects:#"otherData", nil];
[self.myCollectionView performBatchUpdates:^{
int resultsSize = [self.data count]; //data is the previous array of data
[self.data addObjectsFromArray:newData];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
for (int i = resultsSize; i < resultsSize + newData.count; i++) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i
inSection:0]];
}
[self.myCollectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
} completion:nil];
}
If you are inserting multiple items into UICollectionView, you can use performBatchUpdates:
[self.collectionView performBatchUpdates:^{
// Insert the cut/copy items into data source as well as collection view
for (id item in self.selectedItems) {
// update your data source array
[self.images insertObject:item atIndex:indexPath.row];
[self.collectionView insertItemsAtIndexPaths:
[NSArray arrayWithObject:indexPath]];
}
} completion:nil];
– insertItemsAtIndexPaths: does the job
Here is how to insert an item in Swift 3 :
let indexPath = IndexPath(row:index, section: 0) //at some index
self.collectionView.insertItems(at: [indexPath])
You must first update your data.
I've created a UITableview with sections that are clickable. When you click on them,
they "expand" to reveal cells within them
the clicked section scrolls to the top of the view.
I calculate all of the indexpaths to insert/delete the necessary cells and then insert them with the following code:
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:pathsToOpen withRowAnimation:insertAnimation];
[self.tableView deleteRowsAtIndexPaths:pathsToClose withRowAnimation:deleteAnimation];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:[pathsToOpen objectAtIndex:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
There's only one problem- the sections below the selected section are hidden. The first screen-shot shows how the tableview should look. The second screen-shot shows how it actually looks.
If you scroll up (so the hidden sections are offscreen) and then scroll back down, the hidden sections are brought back (once again visible). My guess as to why this is happening is the following:
The insert/delete animations are happening at the same time as the scrollToRowAtIndexPath and it is confusing the TableView. If I hadn't done scrollToRowAtIndexPath sections 3 & 4 would have been offscreen - and so the tableView somehow still thinks they are offscreen. UITableview hides cells/sections that are offscreen as an optimization. If I call scrollToRowAtIndexPath with a dispatch_after with 2 seconds, then sections 3 & 4 are displayed correctly.
So I think I know why this is happening, but I don't know how to fix/override this UITableview optimization. Actually, if I implement scrollViewDidEndScrollingAnimation and then add a breakpoint in this function, the app displays sections 3 & 4 correctly (that's how I got the first screen-shot). But once continuing from this function, the cells disappear.
The full project can be downloaded here
Additional implementation details: Sections are legitimate UITableView sections. I've added a tapGestureRecognizer that triggers a delegate callback to the tableview. Included below is the entire method that opens the sections.
- (void)sectionHeaderView:(SectionHeaderView *)sectionHeaderView sectionOpened:(NSInteger)sectionOpened
{
// Open
sectionHeaderView.numRows = DefaultNumRows;
sectionHeaderView.selected = YES;
NSMutableArray *pathsToOpen = [[NSMutableArray alloc] init];
for (int i = 0; i < sectionHeaderView.numRows; i++)
{
NSIndexPath *pathToOpen = [NSIndexPath indexPathForRow:i inSection:sectionOpened];
[pathsToOpen addObject:pathToOpen];
}
// Close
NSMutableArray *pathsToClose = [[NSMutableArray alloc] init];
if (openSectionHeader)
{
for (int i = 0; i < openSectionHeader.numRows; i++)
{
NSIndexPath *pathToClose = [NSIndexPath indexPathForRow:i inSection:openSectionHeader.section];
[pathsToClose addObject:pathToClose];
}
}
// Set Correct Animation if section's already open
UITableViewRowAnimation insertAnimation = UITableViewRowAnimationBottom;
UITableViewRowAnimation deleteAnimation = UITableViewRowAnimationTop;
if (!openSectionHeader || sectionOpened < openSectionHeader.section)
{
insertAnimation = UITableViewRowAnimationTop;
deleteAnimation = UITableViewRowAnimationBottom;
}
openSectionHeader.numRows = 0;
openSectionHeader.selected = NO;
openSectionHeader = sectionHeaderView;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:pathsToOpen withRowAnimation:insertAnimation];
[self.tableView deleteRowsAtIndexPaths:pathsToClose withRowAnimation:deleteAnimation];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:[pathsToOpen objectAtIndex:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
From what I can tell, the problem is occurring when returning a section view that's already been used. Instead of:
- (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section
{
return [self.sectionHeaderViews objectAtIndex:section];
}
I get no problem if I create a new view each time:
- (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section{
SectionHeaderView *sectionHeaderView = [self.tableView dequeueReusableCellWithIdentifier:SectionHeaderView_NibName];
sectionHeaderView.textLabel.text = [NSString stringWithFormat:#"Section %d", section];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:sectionHeaderView action:#selector(handleTap:)];
[sectionHeaderView addGestureRecognizer:tapRecognizer];
sectionHeaderView.section = section;
sectionHeaderView.delegate = self;
return sectionHeaderView;
}
It's possible this is occurring because you're using [self.tableView dequeueReusableCellWithIdentifier:SectionHeaderView_NibName]; to create section headers and hold on to them in an array, which I don't think UITableViewCell was created for, but I'm not certain. You may want to consider foregoing UITableViewCell for section views and instead use something else (perhaps a UIImageView with a UILabel). Or you can just not store the Section Views in an array...the way you currently have your code set up, you don't need the array and creating a new view is trivial enough you don't need to worry about it.
#AaronHayman's answer works (and IMO the accept and bounty should go to him, as it stands - this just didn't fit in a comment!), but I would go further - you shouldn't be using a cell at all for section header, and you shouldn't be using the dequeue mechanism to essentially load a nib.
Section header view's aren't supposed to be cells, and you may get unforseen effects by using them in place of regular views, particularly if they are deqeueued - the table is keeping a list of these reusable cells when you do that, and recycles them when they go off screen, but your section headers aren't reusable, you have one per section.
In your sample project, I changed the superclass of SectionHeaderView to be a plain UIView, and changed your createSectionHeaderViews method to load directly from the nibs there:
NSMutableArray *sectionHeaderViews = [[NSMutableArray alloc] init];
UINib *headerNib = [UINib nibWithNibName:SectionHeaderView_NibName bundle:nil];
for (int i = 0; i < 5; i++)
{
SectionHeaderView *sectionHeaderView = [[headerNib instantiateWithOwner:nil options:nil] objectAtIndex:0];
sectionHeaderView.textLabel.text = [NSString stringWithFormat:#"Section %d", i];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:sectionHeaderView action:#selector(handleTap:)];
[sectionHeaderView addGestureRecognizer:tapRecognizer];
sectionHeaderView.section = i;
sectionHeaderView.delegate = self;
[sectionHeaderViews addObject:sectionHeaderView];
}
self.sectionHeaderViews = sectionHeaderViews;
I also commented out the register for reuse line from your viewDidLoad. This prevents the section headers from disappearing.