Move cells of UICollectionView in iOS? - ios

I have a UICollectionView.I am trying to give it as SpringBoard functionality.I have am able to give the shake animation to each cell.But i want when icons are shaking then i should be able to move them as well.
For shaking the cells i have added the UILongPressGesture on each cell.When gesture end then i have added one custom animation on them & also added a delete button on top left corner.
Code for long press gesture:
declaration of variables
CGPoint p;
UILongPressGestureRecognizer *lpgr;
NSIndexPath *gesture_indexPath;
add gesture to collection view
lpgr
= [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(handleLongPress:)];
lpgr.minimumPressDuration = .3; // To detect after how many seconds you want shake the cells
lpgr.delegate = self;
[self.collection_view addGestureRecognizer:lpgr];
lpgr.delaysTouchesBegan = YES;
Callback method
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateEnded)
{
return;
}
p = [gestureRecognizer locationInView:self.collection_view];
NSIndexPath *indexPath = [self.collection_view indexPathForItemAtPoint:p];
if (indexPath == nil)
{
NSLog(#"couldn't find index path");
}
else
{
[[NSUserDefaults standardUserDefaults]setValue:#"yes" forKey:#"longPressed"];
[self.collection_view reloadData];
}
}
Cell for Item at inde path
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"arr_album index row");
BlogAlbumCell *cell;
static NSString *identifier = #"UserBlogAlbum";
cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UserAlbum *user_allbum=[arr_userAlbums objectAtIndex:indexPath.row];
cell.label_blog_name.text=user_allbum.album_name;
cell.image_blog_image.image = [UIImage imageNamed:#"more.png"];
[cell.image_blog_image setImageWithURL:[NSURL URLWithString:[IMAGE_BASE_URL stringByAppendingString:user_allbum.album_image]]];
if([[[NSUserDefaults standardUserDefaults]valueForKey:#"longPressed"] isEqualToString:#"yes"])
{
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
[anim setToValue:[NSNumber numberWithFloat:0.0f]];
[anim setFromValue:[NSNumber numberWithDouble:M_PI/50]];
[anim setDuration:0.1];
[anim setRepeatCount:NSUIntegerMax];
[anim setAutoreverses:YES];
cell.layer.shouldRasterize = YES;
[cell.layer addAnimation:anim forKey:#"SpringboardShake"];
CGFloat delButtonSize = 20;
UIButton *delButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, delButtonSize, delButtonSize)];
delButton.center = CGPointMake(9, 10);
delButton.backgroundColor = [UIColor clearColor];
[delButton setImage: [UIImage imageNamed:#"cross_30.png"] forState:UIControlStateNormal];
[cell addSubview:delButton];
[delButton addTarget:self action:#selector(deleteRecipe:) forControlEvents:UIControlEventTouchUpInside];
}
else if ([[[NSUserDefaults standardUserDefaults]valueForKey:#"singleTap"] isEqualToString:#"yes"])
{
for(UIView *subview in [cell subviews])
{
if([subview isKindOfClass:[UIButton class]])
{
[subview removeFromSuperview];
}
else
{
// Do nothing - not a UIButton or subclass instance
}
}
[cell.layer removeAllAnimations];
// _deleteButton.hidden = YES;
// [_deleteButton removeFromSuperview];
}
return cell;
}
It works fine till here.
For moving the cell i made a sample app in which i added UICollectionViewController & override this method
-(void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
NSLog(#"Move at index path called");
}
This also works fine.It also uses long press gesture & when gesture detetced then i am able to move the cells.But now the issue is at one either i can move cell or animate them.If i add my custom gesture then i am not able to move the images.Please tell me how can i remove this issue?

Take a look at this project called DragDropCollectionView. It implements the dragging and dropping as well as the animation.
Edit: This problem can be broken down into 2 smaller subproblems:
How to animate the cells
How to reorder the cells using the drag and drop.
You should combine these 2 solutions into a subclas of UICollectionView to obtain your main solution.
How to animate the cells
To get the wiggle animation you need add 2 different animation effects:
Move the cells vertically i.e up and down bounce
Rotate the cells
Lastly, add a random interval time for each cell so it does appear that the cells don't animate uniformly
Here is the code:
#interface DragDropCollectionView ()
#property (assign, nonatomic) BOOL isWiggling;
#end
#implementation DragDropCollectionView
//Start and Stop methods for wiggle
- (void) startWiggle {
for (UICollectionViewCell *cell in self.visibleCells) {
[self addWiggleAnimationToCell:cell];
}
self.isWiggling = true;
}
- (void)stopWiggle {
for (UICollectionViewCell *cell in self.visibleCells) {
[cell.layer removeAllAnimations];
}
self.isWiggling = false;
}
//- (UICollectionViewCell *)dequ
- (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(nonnull NSIndexPath *)indexPath{
UICollectionViewCell *cell = [super dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
if (self.isWiggling) {
[self addWiggleAnimationToCell:cell];
} else {
[cell.layer removeAllAnimations];
}
return [[UICollectionViewCell alloc] init];
}
//Animations
- (void)addWiggleAnimationToCell:(UICollectionViewCell *)cell {
[CATransaction begin];
[CATransaction setDisableActions:false];
[cell.layer addAnimation:[self rotationAnimation] forKey:#"rotation"];
[cell.layer addAnimation:[self bounceAnimation] forKey:#"bounce"];
[CATransaction commit];
}
- (CAKeyframeAnimation *)rotationAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
CGFloat angle = 0.04;
NSTimeInterval duration = 0.1;
double variance = 0.025;
animation.values = #[#(angle), #(-1 * angle)];
animation.autoreverses = YES;
animation.duration = [self randomizeInterval:duration withVariance: variance];
animation.repeatCount = INFINITY;
return animation;
}
- (CAKeyframeAnimation *)bounceAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.y"];
CGFloat bounce = 3.0;
NSTimeInterval duration = 0.12;
double variance = 0.025;
animation.values = #[#(bounce), #(-1 * bounce)];
animation.autoreverses = YES;
animation.duration = [self randomizeInterval:duration withVariance: variance];
animation.repeatCount = INFINITY;
return animation;
}
- (NSTimeInterval)randomizeInterval:(NSTimeInterval)interval withVariance:(double)variance {
double randomDecimal = (arc4random() % 1000 - 500.0) / 500.0;
return interval + variance * randomDecimal;
}
How to reorder the cells using drag and drop
So the idea is this: You’re not moving the actual cell around, but rather moving a UIImageView with a UIImage of the contents of the cell.
The algorithm goes more or less like this. I’ve broken it down into 3 sections, gestureRecognizerBegan, Changed and Ended
gestureRecognizerBegan:
When the gestureRecognizer begins, determine if the long press was indeed on a cell (and not on empty space)
Get a UIImage of the cell (See my method “getRasterizedImageOfCell”)
Hide the cell (i.e alpha = 0), create a UIImageView with the exact frame of the cell so the user does not realize you’ve actually hidden the cell and you’re actually using the imageview.
gestureRecognizerChanged:
Update the center of the UIImageView so that it moves with your finger.
If the user has stopped moving his finge i.e. he is hovering over the cell he wants to replace, you now need to swap the cells. (Look at my function “shouldSwapCells”, this method returns a bool of whether the cells should swap or not)
Move the cell that you were dragging to the new indexPath. (Look at my method “swapDraggedCell”). UICollectionView has a built-in method called "moveItemAtIndexPath: toIndexPath”, I’m not sure if UITableView has the same thing
gestureRecognizerEnd:
“Drop” the UIImageView back onto the cell
change the cell alpha from 0.0 to 1.0 and remove the UIImageView from the view.
Here is the code:
#interface DragDropCollectionView ()
#property (strong, nonatomic) NSIndexPath *draggedCellIndexPath;
#property (strong, nonatomic) UIImageView *draggingImageView;
#property (assign, nonatomic) CGPoint touchOffsetFromCenterOfCell;
#property (strong, nonatomic) UILongPressGestureRecognizer *longPressRecognizer;
#end
#implementation DragDropCollectionView
- (void)handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer {
CGPoint touchLocation = [longPressRecognizer locationInView:self];
switch (longPressRecognizer.state) {
case UIGestureRecognizerStateBegan: {
self.draggedCellIndexPath = [self indexPathForItemAtPoint:touchLocation];
if (self.draggedCellIndexPath != nil) {
UICollectionViewCell *draggedCell = [self cellForItemAtIndexPath:self.draggedCellIndexPath];
self.draggingImageView = [[UIImageView alloc] initWithImage:[self rasterizedImageCopyOfCell:draggedCell]];
self.draggingImageView.center = draggedCell.center;
[self addSubview:self.draggingImageView];
draggedCell.alpha = 0.0;
self.touchOffsetFromCenterOfCell = CGPointMake(draggedCell.center.x - touchLocation.x, draggedCell.center.y - touchLocation.y);
[UIView animateWithDuration:0.4 animations:^{
self.draggingImageView.transform = CGAffineTransformMakeScale(1.3, 1.3);
self.draggingImageView.alpha = 0.8;
}];
}
break;
}
case UIGestureRecognizerStateChanged: {
if (self.draggedCellIndexPath != nil) {
self.draggingImageView.center = CGPointMake(touchLocation.x + self.touchOffsetFromCenterOfCell.x, touchLocation.y + self.touchOffsetFromCenterOfCell.y);
}
float pingInterval = 0.3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(pingInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSIndexPath *newIndexPath = [self indexPathToSwapCellWithAtPreviousTouchLocation:touchLocation];
if (newIndexPath) {
[self swapDraggedCellWithCellAtIndexPath:newIndexPath];
}
});
break;
}
case UIGestureRecognizerStateEnded: {
if (self.draggedCellIndexPath != nil ) {
UICollectionViewCell *draggedCell = [self cellForItemAtIndexPath:self.draggedCellIndexPath];
[UIView animateWithDuration:0.4 animations:^{
self.draggingImageView.transform = CGAffineTransformIdentity;
self.draggingImageView.alpha = 1.0;
if (draggedCell != nil) {
self.draggingImageView.center = draggedCell.center;
}
} completion:^(BOOL finished) {
[self.draggingImageView removeFromSuperview];
self.draggingImageView = nil;
if (draggedCell != nil) {
draggedCell.alpha = 1.0;
self.draggedCellIndexPath = nil;
}
}];
}
}
default:
break;
}
}
- (UIImage *)rasterizedImageCopyOfCell:(UICollectionViewCell *)cell {
UIGraphicsBeginImageContextWithOptions(cell.bounds.size, false, 0.0);
[cell.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
return image;
}
- (NSIndexPath *)indexPathToSwapCellWithAtPreviousTouchLocation:(CGPoint)previousTouchLocation {
CGPoint currentTouchLocation = [self.longPressRecognizer locationInView:self];
if (!isnan(currentTouchLocation.x) && !isnan(currentTouchLocation.y)) {
if ([self distanceBetweenPoints:currentTouchLocation secondPoint:previousTouchLocation] < 20.0) {
NSIndexPath *newIndexPath = [self indexPathForItemAtPoint:currentTouchLocation];
return newIndexPath;
}
}
return nil;
}
- (CGFloat)distanceBetweenPoints:(CGPoint)firstPoint secondPoint:(CGPoint)secondPoint {
CGFloat xDistance = firstPoint.x - secondPoint.x;
CGFloat yDistance = firstPoint.y - secondPoint.y;
return sqrtf(xDistance * xDistance + yDistance * yDistance);
}
- (void)swapDraggedCellWithCellAtIndexPath:(NSIndexPath *)newIndexPath {
[self moveItemAtIndexPath:self.draggedCellIndexPath toIndexPath:newIndexPath];
UICollectionViewCell *draggedCell = [self cellForItemAtIndexPath:newIndexPath];
draggedCell.alpha = 0.0;
self.draggedCellIndexPath = newIndexPath;
}
Hope this helps :)

Related

Removing a subview of UICollectionView cell content view crashes if that subview has a animated layer

I have added CABasicAnimation on a layer which is added as sublayer to a custom view.Custom view is then added to collection view cell content view as subview. I want to show indefinite progress like bar moving from left to right until download completes(please refer the attachments). Everything is fine except, once download is completed,For removing the custom view with layer animation, i cannot access that view using the tag. The compiler crashes and throws 'could not load any Objective-C class information. This will significantly reduce the quality of type information available'
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableDictionary *attachDetail = [self getManagedObjectForIndexPath:indexPath];
ZCRMMailAttachmentCollectionViewCell *cell = (ZCRMMailAttachmentCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"cellId" forIndexPath:indexPath];
[self setVisibilityOfRemoveButtonInCell:cell inObj:attachDetail];
[self setVisibilityOfSizeInCell:cell inObj:attachDetail];
cell.attachDetails = attachDetail;
[self addLoadingViewIfNotDownloaded:cell managedObject:attachDetail];
[cell redraw];
return cell;
}
-(void)addLoadingViewIfNotDownloaded:(ZCRMMailAttachmentCollectionViewCell*)cell managedObject:(NSDictionary*)attachDict
{
NSNumber *progress = attachDict[MAIL_ATTACH_DOWNLOAD_PROGRESS];
if(!ZV_IsEmpty(progress))
{
if (isinf([progress floatValue]))
{
[cell addLinearInfiniteProgress];
}
else
{
[cell removeLinearProgress];
}
}
}
-(void)addLinearInfiniteProgress
{
UIView* view = [zTvContentView viewWithTag:1234];
if(!view)
{
UIView *progressView = [UIView new];
progressView.tag = 1234;
progressView.layer.masksToBounds = YES;
[zTvContentView addSubview:progressView];
[progressView setBottom:-1];
[progressView alignTo:VTLayoutAlignLeft|VTLayoutAlignRight];
[progressView setHeight:2];
//ProgressLayer
CGRect frame = self.contentView.bounds;
CALayer * indeterminateLayer = [CALayer layer];
indeterminateLayer.delegate = self;
indeterminateLayer.name = #"progresslayer";
indeterminateLayer.frame = CGRectMake(0, 0, 0.4*CGRectGetWidth(frame), 3);
indeterminateLayer.backgroundColor = [CurTheme() themeActionItemColor].CGColor;
indeterminateLayer.opacity = 1;
[progressView.layer addSublayer:indeterminateLayer];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.duration = 1;
animation.repeatCount = HUGE_VALF;
animation.removedOnCompletion = YES;
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake( CGRectGetMinX(frame) - indeterminateLayer.frame.size.width + OFFSET, 0)];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetMaxX(frame) + indeterminateLayer.frame.size.width - OFFSET, 0)];
[indeterminateLayer addAnimation:animation forKey:#"position"];
}
}
(void)removeLinearInfiniteProgress
{
UIView* progressView = [zTvContentView viewWithTag:1234];
if(progressView)
{
if ([progressView superview])
{
[progressView removeFromSuperview];
}
}
}
Any help would be appreciated.

Horizontal reordering of UICollectionView cells

I've got a UICollectionView with horizontal scrolling. Each cell corresponds to a object (a currency code) in an array. What I would like is to be able to reorder cells via a drag and drop gesture.
I found a tutorial for UITableView and tried it but when I hold and drag a cell it only moves vertically and doesn't scroll when I move my finger to the edge of the screen. Here is a gif.
What I want to happen is for the cell to move horizontally, instead of vertically, and for the collection view to scroll when the edge of the screen is reached. How would I achieve this?
This is what I have now:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: #selector(longPressGestureRecognised:)];
[self.collectionView addGestureRecognizer: longPress];
-(IBAction) longPressGestureRecognised:(id)sender
{
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
CGPoint location = [longPress locationInView: self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint: location];
static UIView *snapshot = nil;
static NSIndexPath *sourceIndexPath = nil;
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
sourceIndexPath = indexPath;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath: indexPath];
// Take a snapshot of the selected item using helper method.
snapshot = [self customSnapshotFromView: cell];
// Add the snapshot as subview, centered at cell's centre.
__block CGPoint centre = cell.center;
snapshot.center = centre;
snapshot.alpha = 0.0;
[self.collectionView addSubview: snapshot];
[UIView animateWithDuration: 0.25 animations:^{
// Offset for gesture location.
centre.y = location.y;
snapshot.center = centre;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
// Fade out.
cell.alpha = 0.0;
} completion: ^(BOOL finished) {
cell.hidden = YES;
}];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint centre = snapshot.center;
centre.y = location.y;
snapshot.center = centre;
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual: sourceIndexPath]) {
// Update data source.
[currencyArray exchangeObjectAtIndex: indexPath.item withObjectAtIndex:sourceIndexPath.item];
// Move the items.
[self.collectionView moveItemAtIndexPath: sourceIndexPath toIndexPath: indexPath];
// And update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
}
break;
}
default: {
// Clean up.
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath: sourceIndexPath];
cell.hidden = NO;
cell.alpha = 0.0;
[UIView animateWithDuration: 0.25 animations: ^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.0;
// Undo fade out.
cell.alpha = 1.0;
}completion: ^(BOOL finished) {
sourceIndexPath = nil;
[snapshot removeFromSuperview];
snapshot = nil;
}];
break;
}
}
}
-(UIView *) customSnapshotFromView:(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.
UIView *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;
}
EDIT:
I've figured it out. Here is the code:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: #selector(handleLongGesture:)];
[self.collectionView addGestureRecognizer: longPress];
-(IBAction) handleLongGesture: (id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
CGPoint location = [longPress locationInView: self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint: location];
switch (longPress.state) {
case UIGestureRecognizerStateBegan:
[self.collectionView beginInteractiveMovementForItemAtIndexPath: indexPath];
NSLog(#"Gesture began");
break;
case UIGestureRecognizerStateChanged:
[self.collectionView updateInteractiveMovementTargetPosition: [longPress locationInView: longPress.view]];
NSLog(#"Gesture state changed");
break;
case UIGestureRecognizerStateEnded:
[self.collectionView endInteractiveMovement];
NSLog(#"Gesture state ended");
break;
default:
[self.collectionView cancelInteractiveMovement];
NSLog(#"Gesture cancelled");
break;
}
}
in UIGestureRecognizerStateChanged:
change this line of code
CGPoint centre = snapshot.center;
centre.x = location.x;
snapshot.center = centre;
centre.y returns y axis dragging
change it to centre.x for x axis dragging

UITableView returning wrong indexPath when user's finger goes outside the screen

I'm developing a reorder cells class to allow my users to reorder the cells inside a table.
The process is very simple, I switch cell's position each time I ove one of them.
The process run pretty well until I go down my finger outside the iPhone screen. In that case, the UIGestureRecognizer or the UITableView give me a wrong indexPath. It refers to the first position of my tableView.
This is my code:
Initialize gesture recognition
...
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(longPressGestureRecognized:)];
[_tableView addGestureRecognizer:longPress];
...
Method to control the gesture states
- (IBAction)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
CGPoint location = [longPress locationInView:_tableView];
NSIndexPath *indexPath = [_tableView indexPathForRowAtPoint:location];
static UIView *snapshot = nil; ///< A snapshot of the row user is moving.
static NSIndexPath *sourceIndexPath = nil; ///< Initial index path, where gesture begins.
NSLog(#"State: %ld", (long)state);
switch (state) {
case UIGestureRecognizerStateBegan: {
NSLog(#"UIGestureRecognizerStateBegan");
if (indexPath) {
sourceIndexPath = indexPath;
Task *sourceTask = [_elements objectAtIndex:sourceIndexPath.row];
NSLog(#"superTask: state[%ld], %#, indexpath.row: %#, actualPosition: %#", (long)state, sourceTask.subtask, #(indexPath.row), sourceTask.position);
if (_collapseSubElements){
if ([sourceTask.subtask isEqualToString:#"0"]){
[self collapseAllTaskWithSubtasks];
sourceIndexPath = indexPath = [NSIndexPath indexPathForRow:[_elements indexOfObjectIdenticalTo:sourceTask] inSection:0];
}
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;
}];
}
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.y = location.y;
snapshot.center = center;
Task *sourceTask = [_elements objectAtIndex:sourceIndexPath.row];
Task *targetTask = [_elements objectAtIndex:indexPath.row];
NSLog(#"UIGestureRecognizerStateChanged: %ld - %ld - %#", (long)indexPath.row, (long)sourceIndexPath.row, sourceTask.position);
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
if (indexPath.row - sourceIndexPath.row <= 1){
UITableViewCell *cell = [_tableView cellForRowAtIndexPath:indexPath];
UIButton *subtasksButton = (UIButton *)[cell viewWithTag:108];
// Solamente se pueden mover tareas dentro del mismo nivel
if ([sourceTask isBrotherOfTask:targetTask]){
if (_collapseSubElements){
if (subtasksButton.selected){
//[self expandCollapseSubtasks:subtasksButton];
_collapseCellsBlock(subtasksButton);
}
// ... update element position
NSNumber *sourceTaskPosition = sourceTask.position;
NSNumber *targetTaskPosition = targetTask.position;
_temporalValues = [NSMutableDictionary dictionaryWithObjectsAndKeys:
sourceTaskPosition, #"position", nil];
[targetTask updateWithValues: _temporalValues];
_temporalValues = [NSMutableDictionary dictionaryWithObjectsAndKeys:
targetTaskPosition, #"position", nil];
[sourceTask updateWithValues: _temporalValues];
// ... update data source.
[_elements exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[_tableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
} else {
}
}
}
}
break;
}
case UIGestureRecognizerStateEnded:
{
NSLog(#"UIGestureRecognizerStateEnded");
Task *task = [_elements objectAtIndex:indexPath.row];
//if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
NSLog(#"httpClient indexpath.row: %#, editedposition: %#", #(indexPath.row), task.position);
/*[_httpClient createUpdateRequestForObject:task withPath:#"task/" withRegeneration:NO];
[_httpClient update:nil];*/
_completionBlock(task);
}
default: {
// Clean up.
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) {
sourceIndexPath = nil;
[snapshot removeFromSuperview];
snapshot = nil;
}];
break;
}
}
}
Method to create a snapshot from the selected cell
- (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.
UIView *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;
}
Cell movements
When I move cell number 8 everything is OK, but when I move cell 6 to the bottom, the interface maintain the cell in the right position but the indexPath returned is 0
I think the reason is contentsize from _tableView is smaller than the location you point.
You can check this:
if (_tableView.contentSize.height<location.y) indexPath = [NSIndexPath indexPathForRow:[_tableView numberOfRowsInSection:0]-1 inSection:0];

UICollectionView Drag and Drop update all affected indexPaths

I have a UICollectionView that uses drag and drop functionality to perform manual re-order of the cells.
I only have 1 section that contains two columns of equally sized cells.
To explain my problem properly I would need to upload screenshots but I don't have enough rep, so I will have to explain using numbers.
1 2 3 4 5 6
This represents a CollectionView with 6 cells in 2 columns filling the entire screen.
So the problem i'm encountering is that if I swap, for example, cell 3 and cell 2 everything works correctly because cell 3 becomes cell 2 and vice versa, the issue happens when I swap cell 3 and cell 1 because then cell 3 becomes cell1 and cell1 becomes cell2 and cell2 becomes cell3.
Now it re-arranges but as soon as the app calls [self.collectionView reloadData]; cell3 will stay at 1 where I dropped it, but the new cell 2 and 3 will swap positions as if there indexes havn't updated.
A long press gesture handles the remove and insert of the cell(snapshot of cell) when dragged and all the tiles re-arrange and fit properly into the sections.
-(IBAction)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
CGPoint location = [longPress locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location];
static UIView *snapshot = nil;
static NSIndexPath *sourceIndexPath = nil;
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
sourceIndexPath = indexPath;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
snapshot = [self customSnapshotFromView:(cell)];
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.98;
[self.collectionView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
center.y = location.y;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
cell.hidden = YES;
} completion:nil];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.y = location.y;
center.x = location.x;
snapshot.center = center;
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
[people2 exchangeObjectAtIndex:indexPath.item withObjectAtIndex:sourceIndexPath.item];
[self.collectionView moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];
sourceIndexPath = indexPath;
[databaseClass arrangeData:people2];
}
break;
}
default: {
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:sourceIndexPath];
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.0;
cell.hidden = NO;
} completion:^(BOOL finished){
[snapshot removeFromSuperview];
snapshot = nil;
}];
sourceIndexPath = nil;
break;
}
}
}
I am using a sqlite method to store the arrangement of the tiles in the database.
+(void)arrangeData:(NSArray*)arrange {
[self databaseInit];
if (sqlite3_open(dbpath, &peopleDB ) == SQLITE_OK)
{
for (int i = 0; i < [arrange count]; i++) {
NSString *insertSQL = [NSString stringWithFormat:#"UPDATE PEOPLE SET ARRANGE = %i WHERE ID = %i",i+1,[[arrange objectAtIndex:i] ID]];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2( peopleDB, insert_stmt,-1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
NSLog(#"Arranged Successfully");
}
else {
NSLog(#"%s SQLITE_ERROR '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(peopleDB), sqlite3_errcode(peopleDB));
}
sqlite3_finalize(statement);
}
sqlite3_close(peopleDB);
}
Not that it matters much but the snapshot :
-(UIView *)customSnapshotFromView:(UIView *)inputView {
UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:YES];
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;
}
If anyone has any ideas on how to get the cells to stay where they are when the longpress ends I would be hugely appreciative.
I don't understand sqlite, but according to what you say, it seems that you swap data instead of moving the concerned data.
In your example you have to delete dataForCell3 from it's array and then insert it at the index of dataForCell1.
Seems that instead of that you swapped dataForCell3 and dataForCell1.

After integrating the custom Table, how to take the lable view from that constructed table?

I'm trying to animate a UITableView and running into difficulties. I have loaded the table using custom cells, and I need to get that table's elements again, and move the content of that row downwards in an animation.
While I perform this, I can get only the entire table from the UIView. Actually I want to get the custom table cell content from the table and perform the animation on that.
I'm struggling with:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UILabel *temp=[[UILabel alloc]init];
temp.text=nil;
UIView *anim = nil;
id currentObject;
for (UIView *theview in destination.superview.subviews)
{
if ([theview isKindOfClass:[UITableView class]])
{
NSLog(#"%#",theview);
UIView *vi= theview;//(UITableView *)[UITableView class];
NSLog(#"%#",vi);
for(UIView *vi1 in vi.superview.subviews )
{
if ([vi1 isKindOfClass:[UITableViewCell class]])
{
NSLog(#"%#",vi1);
}
anim = theview;
break;
}
}
}
How do I get the data from that?
now i am just created the custom UILable and assigned my cell text to it and then animated that....
here is the code...
in the table viewDidSelect method i coded this....
NSArray *visibleCells = [tableView visibleCells];
NSLog(#"the count %d",[visibleCells count]);
//UITableViewCell *aCell=[tableView cellForRowAtIndexPath:indexPath];
UITableViewCell *visiblecell=[tableView cellForRowAtIndexPath:indexPath];
NSLog(#"the count %#",aCell);
NSLog(#"The visible cell ::%#",visiblecell);
int i=0;
for (UITableViewCell *theCell in visibleCells)
{
if ([theCell isEqual:visiblecell])
{
selectedView.frame =CGRectMake( 0,i*(480/[visibleCells count]),320,50);
break;
}
i++;
}
anim=selectedView;
if (!anim)
return;
[self pathGenerate:anim];
and i having the separate animate function just i called that...
- (void)pathGenerate:(UIView *)anim
{
// [self.tabBarController.tabBar insertSubview:(UIImageView*)[UIImage imageNamed:#"Coffee_smoke.png"] atIndex:0];
self.tabBarItem.image=nil;
UIBezierPath *movePath = [UIBezierPath bezierPath];
[movePath moveToPoint:anim.center];//-->down---->destinationPoint.center
[movePath addQuadCurveToPoint:CGPointMake(170, 420)
controlPoint:CGPointMake(200,10)];/* // 2nd phase //destinationPoint.center.x+200, destinationPoint.center.y-100)];*/
CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnim.path = movePath.CGPath;
moveAnim.removedOnCompletion = YES;
CABasicAnimation *scaleAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
scaleAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
scaleAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)];
scaleAnim.removedOnCompletion = YES;
CABasicAnimation *opacityAnim = [CABasicAnimation animationWithKeyPath:#"alpha"];
opacityAnim.fromValue = [NSNumber numberWithFloat:1.0];
opacityAnim.toValue = [NSNumber numberWithFloat:0.1];
opacityAnim.removedOnCompletion = YES;
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.animations = [NSArray arrayWithObjects:moveAnim, scaleAnim, opacityAnim, nil];
animGroup.duration = 0.5;
[anim.layer addAnimation:animGroup forKey:nil];
// self.tabBarItem.image=[UIImage imageNamed:#"Coffee_smoke.png"];
}

Resources