How to limit UITableView row reordering to a section

IosIphoneUitableviewCocoa Touch

Ios Problem Overview


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!

Ios Solutions


Solution 1 - Ios

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;
}

Solution 2 - Ios

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;
}
}
  • (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath { if( sourceIndexPath.section != proposedDestinationIndexPath.section ) { return sourceIndexPath; } else { return proposedDestinationIndexPath; } }

Solution 3 - Ios

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
}

Solution 4 - Ios

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;
    }
}

Solution 5 - Ios

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
}

Solution 6 - Ios

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;
    }

Solution 7 - Ios

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
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionphilsquaredView Question on Stackoverflow
Solution 1 - IosJason HarwigView Answer on Stackoverflow
Solution 2 - IosphilsquaredView Answer on Stackoverflow
Solution 3 - IosAlexanderNView Answer on Stackoverflow
Solution 4 - IosAndyView Answer on Stackoverflow
Solution 5 - IosHenryView Answer on Stackoverflow
Solution 6 - IosBkillnestView Answer on Stackoverflow
Solution 7 - IosayalcinView Answer on Stackoverflow