The UICollectionView "swipe-away" in iOS7 app manager?
Asked Answered
Z

2

9

On any 2014+ iPhone or iPad, double-click the home button to see the "app manager"

enter image description here

This is a left-right UICollectionView BUT it has a "swipe-away" gesture .. swipe up. How is it done? It's not so easy to "remove" a cell from a UICollectionView.


Footnote for googlers .. for the general problem of "peeling off", "tearing away", one cell from a collection view, here's a full tidy explanation: https://mcmap.net/q/1173605/-can-i-quot-take-out-quot-or-quot-tear-off-quot-a-cell-from-a-uicollectionview Hope it helps someone.

Zellers answered 11/4, 2014 at 9:35 Comment(8)
Internally, this is not a collection view. It's a series of scrollviews, managed by a controller. As a matter of fact, I am working on something similar, but nothing to announce yet.Alvis
Ahhh ...**It's a series of scrollviews, managed by a controller** Typical incredible insight from Leo! :OZellers
Joe, you can attach the debugger to SpringBoard on the simulator and inspect the view hierarchy.Alvis
Hmm .. that's one of those things that sounds easy when YOU say it :) Thanks though I will deeply look in to that! Can you make that last comment an answer as it is so useful???Zellers
Let me see if I can come up with a real solution in time.Alvis
Well, either this is a horizontal UICollectionView or a horizontal UITableView... TableViews are (in my opinion) easier to handle modifications and transitions with UITableViewCell inserts and deletions. Maybe this could help you?Doorn
@JoeBlow If I recall correctly, there are two horizontal scrollviews, one for the cards and another for the icons, and each card is actually a horizontal scrollview. So when you "swipe to remove", you actually scroll the content of the scrollview, and when a certain content offset is hit, the application is closed and the other items are moved.Alvis
Leo - I guess that makes perfect sense. You rock. You know, I just noticed they wait until there is absolutely no physicss on the left-right scrollview, before the centered up-down scroll view is enabled. I've been looking in to the obscure UIViewControllerAnimatedTransitioning and also UICollectionViewTransitionLayout.Zellers
K
14

It can be much simpler than the comments on your question are suggesting.

Your cell should contain a view (the thing that you're going to drag off) and you add a UIPanGestureRecognizer to that view.

In the gesture's action method, you move the view up or down, and when it gets far enough off that you want to delete it, you just animate it off. There are plenty of questions here dealing with this part.

This leaves a gap in your collection and now you need to move things around. It turns out this is quite simple:

[_collectionView performBatchUpdates:^{
   [_collectionView deleteItemsAtIndexPaths:@[indexPath]];
} completion:^(BOOL finished) {
     // you might want to remove the data from the data source here so the view doesn't come back to life when the collection view is reloaded.
}];

The stuff to the right of the removed cell slides over and we're all good.

Another problem to get over: making sure your gesture recognizer and the collection view's one play nice together. Thankfully, that's not too tricky either.

[_collectionView.panGestureRecognizer requireGestureRecognizerToFail:pgr]; //where pgr is the recognizer you made for dragging the view off

This means in order for the collection view's pan gesture to do its thing, your one has to fail. So you'll want to set yours up so that it only works when panning up and down, and let the collection view still do its thing for left to right pans. In your gesture recognizers's delegate, implement the following method which simply checks if you're moving more on the x-axis or y-axis.

-(BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
    CGPoint translation =[gestureRecognizer translationInView:self.view];

    return(translation.x * translation.x > translation.y * translation.y);
}
Kelikeligot answered 14/4, 2014 at 7:9 Comment(3)
When I used this approach the collection view I was using took up the full size of the screen, but I expect you can tell the collection view not to clip and it won't. I'll test it out and report back later.Kelikeligot
No prob, just tried it out by unchecking 'Clip Subviews' for the cell in the storyboard and that works.Kelikeligot
You can also set masksToBounds to YES on the view's layer, if you are doing it programmatically w/o storyboard. I did this myself when I implemented a collection view where I had to drag items out of the cv on top of another view. It's worth nothing that you can animate from performBatchUpdates:completion: if needed.Frendel
M
1

I was looking for this functionality and using @mbehan suggestion i faked this functionality using UICollectionView.

What i did is i added a view of smaller size on a collection cell(Transparent background) and added a single pan gesture on CollectionView(not on each cell) then on pan gesture i move the view and it looks like the cell is moving. After view reaches some point i first hide it and then deletes the collection view cell.

Cell Hierarchy : collectionViewCell -> View(tag value==2) -> UILabel(tag Value == 1) Label is just used for placeholder purpose.

i am posting my code below:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = (UICollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"Cards" forIndexPath:indexPath];
    UILabel *lblNumber = (UILabel*)[cell.contentView viewWithTag:1];
    UIView *viewTouch = (UIView*)[cell.contentView viewWithTag:2];
    [viewTouch setHidden:NO];
    [lblNumber setText:arrCards[indexPath.row]];

    return cell;
}


- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(0, 50, 0, 30);
  }

-(BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{

    if([gestureRecognizer isEqual:panGesture]) {
    CGPoint point = [(UIPanGestureRecognizer*)gestureRecognizer translationInView:collectionView_];
        if(point.x != 0) { //adjust this condition if you want some leniency on the X axis
        //The translation was on the X axis, i.e. right/left,
        //so this gesture recognizer shouldn't do anything about it
        return NO;
        }
   }
   return YES;
}

- (IBAction)panGestureCalled:(UIPanGestureRecognizer *)sender {
    yFromCenter = [sender translationInView:collectionView_].y; //%%% positive for up, negative for down

    UIView *view = sender.view;
    CGPoint location = [view.superview convertPoint:view.center toView:collectionView_];
    NSIndexPath *indexPath = [collectionView_ indexPathForItemAtPoint:location];
    UICollectionViewCell *cell = [collectionView_ cellForItemAtIndexPath:indexPath];
    UIView *touchView = (UIView*)[cell.contentView viewWithTag:2];


    switch (sender.state) {
      case UIGestureRecognizerStateBegan:{
        originalPoint = touchView.center;
        break;
    };
      case UIGestureRecognizerStateChanged:{
        touchView.center = CGPointMake(originalPoint.x , originalPoint.y + yFromCenter);

        break;
    };
        //%%% let go of the card
      case UIGestureRecognizerStateEnded: {
        CGFloat velocityY = (0.2*[(UIPanGestureRecognizer*)sender velocityInView:collectionView_].y);

        if (velocityY < -30 && yFromCenter<0) {
            [self hideView:touchView withDuration:0.2 andIndexPath:indexPath];

        }else if ((yFromCenter< 0 && yFromCenter > -200) || yFromCenter > 0){

            CGFloat animationDuration = (ABS(velocityY)*.0002)+.2;
            [self resettleViewToOriginalPosition:touchView andDuration:animationDuration];

        }else
            [self hideView:touchView withDuration:0.2 andIndexPath:indexPath];

    };
        break;
      case UIGestureRecognizerStatePossible:break;
      case UIGestureRecognizerStateCancelled:break;
      case UIGestureRecognizerStateFailed:break;
  }
}


-(void)resettleViewToOriginalPosition:(UIView*)view andDuration:(float)duration{
[UIView animateWithDuration:duration
                      delay:0.0f
                    options: UIViewAnimationOptionCurveEaseOut
                 animations:^
 {
     [view setCenter:originalPoint];
 }
                 completion:^(BOOL finished)
 {

 }];
}
- (void)hideView:(UIView*)view withDuration:(float)duration andIndexPath:(NSIndexPath*)indexPath
{

[UIView animateWithDuration:duration
                      delay:0.0f
                    options: UIViewAnimationOptionCurveEaseOut
                 animations:^
 {
     CGRect frame = view.frame;
     frame.origin.y = -300;
     view.frame = frame;
 }
                 completion:^(BOOL finished)
 {
     [view setHidden:YES];
     CGRect frame = view.frame;
     frame.origin.y = 39;
     view.frame = frame;
     NSLog(@"View is hidden.");

     [arrCards removeObjectAtIndex:indexPath.row];
     [collectionView_ performBatchUpdates:^{
         [collectionView_ deleteItemsAtIndexPaths:@[indexPath]];
     } completion:^(BOOL finished) {
         // you might want to remove the data from the data source here so the view doesn't come back to life when the collection view is reloaded.
     }];
 }];
}

and keep pagingEnabled of CollectionView to NO and then it should be good to go.

Mitchel answered 21/9, 2015 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.