How to limit UITableView row reordering to a section

I was hitting my head over this one, and google was turning up nothing. I eventually worked it out and thought I'd write it up here for the sake of the next person.

You have a UITableView with multiple sections. Each section is homogeneous, but the table overall is heterogeneous. So you might want to allow re-ordering of rows within a section, but not across sections. Maybe you only even want want one section to be reorderable at all (that was my case). If you're looking, as I was, at the UITableViewDataSourceDelegate you won't find a notification for when it is about to let you move a row between sections. You get one when it starts moving a row (which is fine) and one when it's already moved it and you get a chance to sync with your internal stuff. Not helpful.

So how can you prevent re-orders between sections?

I'll post what I did as a separate answer, leaving it open for someone else to post an even better answer!

32695 次浏览

Simple enough, really.

The UITableViewDelegate has the method:


tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:

This gets called while the user is hovering over a potential drop point. You get a chance to say, "no! don't drop it there! Drop it over here instead". You can return a different index path to the proposed one.

All I did was check if the section indices match. If they do then great, return the proposed path. if not, return the source path. This also nicely prevents the rows in other sections even moving out of the way as you drag - and the dragged row will snap back to it's original position of you try to move it to another section.


- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if( sourceIndexPath.section != proposedDestinationIndexPath.section )
{
return sourceIndexPath;
}
else
{
return proposedDestinationIndexPath;
}
}

This implementation will prevent re-ordering outside of the original section like Phil's answer, but it will also snap the record to the first or last row of the section, depending on where the drag went, instead of where it started.

- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if (sourceIndexPath.section != proposedDestinationIndexPath.section) {
NSInteger row = 0;
if (sourceIndexPath.section < proposedDestinationIndexPath.section) {
row = [tableView numberOfRowsInSection:sourceIndexPath.section] - 1;
}
return [NSIndexPath indexPathForRow:row inSection:sourceIndexPath.section];
}


return proposedDestinationIndexPath;
}

Than @Jason Harwig, the code below works correctly.

- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if (sourceIndexPath.section != proposedDestinationIndexPath.section) {
NSInteger row = 0;
if (sourceIndexPath.section < proposedDestinationIndexPath.section) {
row = [tableView numberOfRowsInSection:sourceIndexPath.section] - 1;
}
return [NSIndexPath indexPathForRow:row inSection:sourceIndexPath.section];
}


return proposedDestinationIndexPath;
}

You can prevent the movement of rows in between section using below method. Just do not allow any movement in between section. You can even control the movement of specific row within a section. e.g last row in a Section.

Here is the example:

- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {


// Do not allow any movement between section
if ( sourceIndexPath.section != proposedDestinationIndexPath.section) {
return sourceIndexPath;
}
// You can even control the movement of specific row within a section. e.g last row in a     Section


// Check if we have selected the last row in section
if (sourceIndexPath.row < sourceIndexPath.length) {
return proposedDestinationIndexPath;
}
else {
return sourceIndexPath;
}
}

Swifty swift version of Jason's answer for you lazy people:

Swift 3, 4 & 5

override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
if sourceIndexPath.section != proposedDestinationIndexPath.section {
var row = 0
if sourceIndexPath.section < proposedDestinationIndexPath.section {
row = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1
}
return IndexPath(row: row, section: sourceIndexPath.section)
}
return proposedDestinationIndexPath
}

Swift 1 & 2

override func tableView(tableView: UITableView, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath, toProposedIndexPath proposedDestinationIndexPath: NSIndexPath) -> NSIndexPath {
if sourceIndexPath.section != proposedDestinationIndexPath.section {
var row = 0
if sourceIndexPath.section < proposedDestinationIndexPath.section {
row = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1
}
return NSIndexPath(forRow: row, inSection: sourceIndexPath.section)
}
return proposedDestinationIndexPath
}

Swift 3:

override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
if sourceIndexPath.section != proposedDestinationIndexPath.section {
var row = 0
if sourceIndexPath.section < proposedDestinationIndexPath.section {
row = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1
}
return IndexPath(row: row, section: sourceIndexPath.section)
}
return proposedDestinationIndexPath
}

For not changing position between sections Swift3

override func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
if originalIndexPath.section != proposedIndexPath.section
{
return originalIndexPath
}
else
{
return proposedIndexPath
}
}