How to get notified of UITableViewCell move start and end?
Asked Answered
L

4

33

I have a UITableView in my iOS app that gets refreshed periodically. The user is also able to move the tableview rows at all times (the tableview is always in editing mode).

I want to stop the refresh timer when the user starts dragging a row and start it again when the row is dropped.

The last part should be easy with moveRowAtIndexPath, but how to get notified about drag start?

Lyallpur answered 27/3, 2012 at 22:3 Comment(0)
C
44

Your UITableViewDelegate will receive the following notifications in response to reordering actions:

- (void)tableView:(UITableView *)tableView willBeginReorderingRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didEndReorderingRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didCancelReorderingRowAtIndexPath:(NSIndexPath *)indexPath;
Candelabrum answered 1/6, 2012 at 16:24 Comment(11)
I do not see these delegate methods declared in the UITableView.h interface. Can you please explain where we can find them?Adventitia
@mprudhom I received the callbacks but they are undocumented. Do you have these delegate methods in a production app? Is this considered private API?Sauveur
These methods are indeed called on delegate of the UITableView. Since they are undocumented, Apple might just break them in future releases of iOS, but from iOS 5 and up to iOS 7, this still works. I have yet to find out whether Apple would actually allow an app with these methods implemented to be published on App Store (good luck to me, I guess), but I don't think there's a good reason not to: if I implement these 3 methods in my UIViewController, and they are not documented and they don't start with underscore or whatever, how are they different from my own methods?Preceptory
Just as a followup: I should mention that my app that uses these three methods has just been approved by Apple, so we should probably consider them safe to use for purpose of improving user experience.Preceptory
The methods are still called on iOS 8.3.Lezlie
Cannot get called in swift?Melitta
Still getting called in iOS 9.2 and Swift 2 with func tableView(tableView: UITableView, didEndReorderingRowAtIndexPath indexPath: NSIndexPath). Such a useful method, I wonder why they aren't documenting it.Assuan
can anyone confirm that it's called on swift 3 and iOS 11?Hinz
Is it still valid ? or is there any alternative?Allmon
For anybody wondering. It's still called in iOS 12 with Swift 4 using the following signature: @objc public func tableView(_: UITableView, willBeginReorderingRowAtIndexPath indexPath: IndexPath)Tipperary
Note that these methods won't get called on iOS 11 or later if you have a dragDelegate set. In that case you can use tableView(_:dragSessionWillBegin:) and tableView(_:dragSessionDidEnd:) to accomplish the same thing. You can leave these methods as a fallback for older iOS versions.Twoedged
O
9

I ran into the same problem some time ago and didn't find a solution. While I started this answer with an explanation why it can't be done, I actually found out how it can be done! :-)

In short: You have to create a custom subclass of UITableViewCell. Override layoutSubviews to attach a UILongPressGestureRecognizer to UITableViewCellReorderControl. Define a protocol and use a delegate to inform whoever you want to about the dragging state.

CustomTableViewCell.h:

#import <UIKit/UIKit.h>

@protocol CustomTableViewCellDelegate;

@interface CustomTableViewCell : UITableViewCell {
}

@property (nonatomic, assign) id <CustomTableViewCellDelegate> delegate;

@end

@protocol CustomTableViewCellDelegate
- (void)CustomTableViewCell:(CustomTableViewCell *)cell isDragging:(BOOL)value;
@end

CustomTableViewCell.m:

#import "CustomTableViewCell.h"

@implementation CustomTableViewCell

@synthesize delegate = _delegate;

- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
        [_delegate CustomTableViewCell:self isDragging:YES];    // Dragging started
    } else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
        [_delegate CustomTableViewCell:self isDragging:NO];     // Dragging ended
    }
}

- (void)layoutSubviews {
    [super layoutSubviews];

    for (UIView *view in self.subviews) {
        if ([NSStringFromClass ([view class]) rangeOfString:@"ReorderControl"].location != NSNotFound) {    // UITableViewCellReorderControl
            if (view.gestureRecognizers.count == 0) {
                UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
                gesture.cancelsTouchesInView    = NO;
                gesture.minimumPressDuration    = 0.150;
                [view addGestureRecognizer:gesture];
            }
        }
    }
}

@end

Be aware that while this code doesn't use any private APIs it still might stop working if Apple changes its internal implementation (i.e. by changing the classname of UITableViewCellReorderControl).

Osteoarthritis answered 28/3, 2012 at 1:8 Comment(3)
Thanks Peter, will need to rethink if my current implementation is worth the risk of relying on the class name or if I better change the "tableview is always editing" paradigm so that I can stop updating it once the user taps editing. Seems more reliable, just not sure how to integrate that in my UI ;)Lyallpur
This was the accepted answer, changed it to the answer from @mprudhom since Apple added and documented new delegate methods.Lyallpur
@Lyallpur Where did Apple added and documented the new delegate methods? I'm on iOS 8 and they are still private API.Gearbox
H
5

Just I found a hack, since apple will reduce the alpha, we can use that i guess

@interface CustomTableViewCell () 
@property (nonatomic, assign) BOOL isDragging;
@end

-(void)draggingWillBegan
{
    //use a delegate method to inform tableview
}

-(void)draggingDidEnd
{
    //use a delegate method to inform tableview
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    //Since apple wont provide en
    if(self.alpha <= 0.95 && !self.isDragging){
        self.isDragging = YES;
        [self draggingWillBegan];
    }

    if(self.alpha >= 0.95 && self.isDragging){
        self.isDragging = NO;
        [self draggingDidEnd];
    }
}
Horthy answered 1/12, 2014 at 4:10 Comment(2)
this is the only thing that worked for me. in my case, the tableview was inside a card view that's also listening for gestures. the dragging of the tableview would be caught up with the card view gestures. I had to "turn off" the card view gestures when I detect "isDragging" in my tableviewcells and return the gestures when the cells are not being dragged.Hinz
Still the only thing working - iOS 17.Lindbergh
L
0

this method gets called when you're done moving cells:

- (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
Londoner answered 22/9, 2014 at 0:17 Comment(2)
No, this method gets called while you're dragging a cell between rows.Noranorah
Kimi Chiu - Nope its called on done moving, not while draggingMusca

© 2022 - 2024 — McMap. All rights reserved.