128

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!

TheNeil
  • 3,321
  • 2
  • 27
  • 52
philsquared
  • 22,403
  • 12
  • 69
  • 98
  • 1
    Which notification are you referring to when you say "you get one when it starts moving a row"? I'm not seeing one but am hoping to discern when cell-reordering begins. – TomSwift Jan 31 '12 at 21:38

7 Answers7

183

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;
}
aopsfan
  • 2,451
  • 19
  • 30
Jason Harwig
  • 43,743
  • 5
  • 43
  • 44
  • Is there a way to prevent the table from auto-scrolling (while dragging a cell) outside of allowed section? – Palimondo Apr 08 '12 at 14:49
  • 1
    In Xamarin, the wrapper method is `UIKit.UITableViewController.CustomizeMoveTarget`, for anyone wondering. – bunkerdive Feb 23 '17 at 19:38
78

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;
    }
}
philsquared
  • 22,403
  • 12
  • 69
  • 98
  • Doing exactly this causes some truly strange display bugs to occur on my setup, and it's notably different from apple's own example code on how to do this. FWIW! not saying it's wrong, just that I wonder if it's actually more complicated than this. – Billy Gray Jun 26 '13 at 16:13
  • I can drag row in other section even that row is not getting set there. but it's visible when to other forcefully.? any idea – Sanoj Kashyap Feb 04 '15 at 10:55
36

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
}
Bilaal Rashid
  • 828
  • 2
  • 13
  • 21
AlexanderN
  • 2,610
  • 1
  • 26
  • 42
  • 2
    Copied it, pasted it. Ran it, loved it. Came back. Thanks, this feels guilty, but I accept! ;) – Joshua Hart Aug 13 '20 at 02:00
  • I am using Swift 5 and I am confusing here. Do we ever need `override` keyword? As, if I were using `override` keyword, I will be getting compiler error. – Cheok Yan Cheng Sep 17 '20 at 11:07
7

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;
    }
}
modusCell
  • 13,151
  • 9
  • 53
  • 80
Andy
  • 2,493
  • 6
  • 37
  • 63
7

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
}
Henry
  • 414
  • 4
  • 9
3

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;
    }
Bkillnest
  • 722
  • 9
  • 16
1

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
    }
}
ayalcin
  • 877
  • 1
  • 8
  • 16