Using long press gesture to reorder cells in tableview?
So essentially you want the "Clear"-like row reordering right? (around 0:15)
This SO post might help.
Unfortunately I don't think you can do it with the present iOS SDK tools short of hacking together a UITableView + Controller from scratch (you'd need to create each row itself and have a UITouch respond relevant to the CGRect of your row-to-move).
It'd be pretty complicated since you need to get the animation of the rows "getting out of the way" as you move the row-to-be-reordered around.
The cocoas tool looks promising though, at least go take a look at the source.
Autoscroll smoothly UITableView while dragging UITableViewCells in iOS app
I have solved my problem with a very beautiful solution so I'm going to explain in three simple steps how to do it.
I have use some inspiration from https://github.com/hpique/HPReorderTableView and share it in my own repository https://github.com/enrimr/EMRReorderTableCells
A. Manage gestureRecognition
longPressGestureRecognized:
- (IBAction)longPressGestureRecognized:(id)sender {
_reorderGestureRecognizer = (UILongPressGestureRecognizer *)sender;
CGPoint location = [_reorderGestureRecognizer locationInView:_tableView];
NSIndexPath *indexPath = [self getCellIndexPathWithPoint:location];
UIGestureRecognizerState state = _reorderGestureRecognizer.state;
switch (state) {
case UIGestureRecognizerStateBegan: {
NSIndexPath *indexPath = [_tableView indexPathForRowAtPoint:location];
if (indexPath == nil)
{
[self gestureRecognizerCancel:_reorderGestureRecognizer];
break;
}
// For scrolling while dragging
_scrollDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(scrollTableWithCell:)];
[_scrollDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// Check for the right indexes (between margins of offset
if (indexPath.row >=_elementsOffset && indexPath.row < [_elements count]+_elementsOffset){
if (indexPath) {
sourceIndexPath = indexPath;
id sourceElement = [_elements objectAtIndex:sourceIndexPath.row-_elementsOffset];
snapshot = [self createSnapshotForCellAtIndexPath:indexPath withPosition:location];
}
} else {
sourceIndexPath = nil;
snapshot = nil;
}
break;
}
case UIGestureRecognizerStateChanged: {
[self calculateScroll:_reorderGestureRecognizer];
if (sourceIndexPath != nil && indexPath.row >=_elementsOffset && indexPath.row < [_elements count]+_elementsOffset){
[self updateSnapshotWithPosition:location];
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
if (indexPath.row - sourceIndexPath.row <= 1){
id sourceElement = [_elements objectAtIndex:sourceIndexPath.row-_elementsOffset];
id targetElement = [_elements objectAtIndex:indexPath.row-_elementsOffset];
sourceIndexPath = [self exchangeElement:sourceElement byElement:targetElement];
}
}
}
break;
}
case UIGestureRecognizerStateEnded:
{
// For scrolling while dragging
[_scrollDisplayLink invalidate];
_scrollDisplayLink = nil;
_scrollRate = 0;
// Check if it is the last element
if (sourceIndexPath != nil){
id element;
if (indexPath.row <=_elementsOffset){
element = [_elements firstObject];
} else if (indexPath.row > [_elements count]-1+_elementsOffset){
element = [_elements lastObject];
} else {
element = [_elements objectAtIndex:indexPath.row-_elementsOffset];
}
}
}
default: {
// Clean up.
[self deleteSnapshotForRowAtIndexPath:sourceIndexPath];
[appDelegate startTimer];
break;
}
}
}
gestureRecognizerCancel:
It is use to cancel gesture recognition to finish the reorder action.
-(void) gestureRecognizerCancel:(UIGestureRecognizer *) gestureRecognizer
{ // See: http://stackoverflow.com/a/4167471/143378
gestureRecognizer.enabled = NO;
gestureRecognizer.enabled = YES;
}
scrollTableWithCell:
The method it is called to make scrolling movement when you are in the limits of the table (up and down)
- (void)scrollTableWithCell:(NSTimer *)timer
{
UILongPressGestureRecognizer *gesture = _reorderGestureRecognizer;
const CGPoint location = [gesture locationInView:_tableView];
CGPoint currentOffset = _tableView.contentOffset;
CGPoint newOffset = CGPointMake(currentOffset.x, currentOffset.y + _scrollRate * 10);
if (newOffset.y < -_tableView.contentInset.top)
{
newOffset.y = -_tableView.contentInset.top;
}
else if (_tableView.contentSize.height + _tableView.contentInset.bottom < _tableView.frame.size.height)
{
newOffset = currentOffset;
}
else if (newOffset.y > (_tableView.contentSize.height + _tableView.contentInset.bottom) - _tableView.frame.size.height)
{
newOffset.y = (_tableView.contentSize.height + _tableView.contentInset.bottom) - _tableView.frame.size.height;
}
[_tableView setContentOffset:newOffset];
if (location.y >= 0 && location.y <= _tableView.contentSize.height + 50)
{
[self updateSnapshotWithPosition:location];
NSIndexPath *indexPath = [self getCellIndexPathWithPoint:location];
// CHeck if element is between offset limits.
if (![indexPath isEqual:sourceIndexPath] &&
indexPath.row >= _elementsOffset &&
indexPath.row - _elementsOffset < [_elements count] &&
sourceIndexPath.row >= _elementsOffset &&
sourceIndexPath.row - _elementsOffset < [_elements count])
{
id sourceElement = [_elements objectAtIndex:sourceIndexPath.row-_elementsOffset];
id targetElement = [_elements objectAtIndex:indexPath.row-_elementsOffset];
[self exchangeElement:sourceElement byElement:targetElement];
sourceIndexPath = indexPath;
}
}
}
B. Snapshot management
createSnapshotForCellAtIndexPath:withPosition
Method that creates a snapshot (a image copy) of the cell you are moving
-(UIView *)createSnapshotForCellAtIndexPath:(NSIndexPath *)indexPath withPosition:(CGPoint)location{
UITableViewCell *cell = [_tableView cellForRowAtIndexPath:indexPath];
// Take a snapshot of the selected row using helper method.
snapshot = [self customSnapshoFromView:cell];
// Add the snapshot as subview, centered at cell's center...
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.0;
[_tableView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
// Offset for gesture location.
center.y = location.y;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
cell.alpha = 0.0;
} completion:^(BOOL finished) {
cell.hidden = YES;
}];
return snapshot;
}
customSnapshoFromView:
Returns a customized snapshot of a given view. */
- (UIView *)customSnapshoFromView:(UIView *)inputView {
// Make an image from the input view.
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, NO, 0);
[inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Create an image view.
snapshot = [[UIImageView alloc] initWithImage:image];
snapshot.layer.masksToBounds = NO;
snapshot.layer.cornerRadius = 0.0;
snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
snapshot.layer.shadowRadius = 5.0;
snapshot.layer.shadowOpacity = 0.4;
return snapshot;
}
updateSnapshotWithPosition:
Given a CGPoint, it changes the snapshot position to show the cell you are moving in the right place of the _tableView
-(void)updateSnapshotWithPosition:(CGPoint)location{
CGPoint center = snapshot.center;
center.y = location.y;
snapshot.center = center;
}
deleteSnapshotForRowAtIndexPath:
When dragging finishes, you need to delete the snapshot from the _tableView
-(void)deleteSnapshotForRowAtIndexPath:(NSIndexPath *)sourceIndexPath{
UITableViewCell *cell = [_tableView cellForRowAtIndexPath:sourceIndexPath];
cell.hidden = NO;
cell.alpha = 0.0;
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.0;
cell.alpha = 1.0;
} completion:^(BOOL finished) {
[snapshot removeFromSuperview];
}];
}
calculateScroll
-(void)calculateScroll:(UIGestureRecognizer *)gestureRecognizer{
const CGPoint location = [gestureRecognizer locationInView:_tableView];
CGRect rect = _tableView.bounds;
// adjust rect for content inset as we will use it below for calculating scroll zones
rect.size.height -= _tableView.contentInset.top;
//[self updateCurrentLocation:gestureRecognizer];
// tell us if we should scroll and which direction
CGFloat scrollZoneHeight = rect.size.height / 6;
CGFloat bottomScrollBeginning = _tableView.contentOffset.y + _tableView.contentInset.top + rect.size.height - scrollZoneHeight;
CGFloat topScrollBeginning = _tableView.contentOffset.y + _tableView.contentInset.top + scrollZoneHeight;
// we're in the bottom zone
if (location.y >= bottomScrollBeginning)
{
_scrollRate = (location.y - bottomScrollBeginning) / scrollZoneHeight;
}
// we're in the top zone
else if (location.y <= topScrollBeginning)
{
_scrollRate = (location.y - topScrollBeginning) / scrollZoneHeight;
}
else
{
_scrollRate = 0;
}
}
C. How to use it
In your init method, assign a gesture recognizer to the table view. Assign as action the method longPressGestureRecognized:
as follows:
_reorderGestureRecognizer = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(longPressGestureRecognized:)];
[_tableView addGestureRecognizer:_reorderGestureRecognizer];
Declare the variables you will need to use the above code explained
@implementation YourClassName{
CADisplayLink *_scrollDisplayLink;
CGFloat _scrollRate;
UIView *snapshot; // A snapshot of the row user is moving.
NSIndexPath *sourceIndexPath; // Initial index path, where gesture begins.
}
And that's everything you will need to solve the problem I had.
Related Topics
Swift - Using Replacerange() to Change Certain Occurrences in a String
iOS Safari Memory Usage with "-Webkit-Transform"
Didreceiveremotenotification: Fetchcompletionhandler: Open from Icon VS Push Notification
What Tool(S) How to Use to Produce iPhone App Screencasts
What Are the Benefits of Using Storyboards Instead of Xib Files in iOS Programming
Navigating to a New Screen When Stream Value in Bloc Changes
Querying iOS Keychain Using Swift
How to Call Method from One Class in Another (Ios)
Working with Live Photos in Playground
Reverse an Audio File Swift/Objective-C
CSS Gradient Not Working on iOS
iOS Swift Multiple Dimension Arrays - Compiliing Takes Ages. What Should I Change
Is There a Public Way to Force Mpnowplayinginfocenter to Show Podcast Controls
Uiscrollview with Centered Uiimageview, Like Photos App
Uisearchbar: Clear Background Color or Set Background Image
How to Combine Two Dictionary Instances in Swift
How to Delete All Objects from My Persistent Store in Core Data