How can I achieve the animation where a UICollectionViewCell with flip and grow to show modal view on tap?
Here is what I used in another project and it was working well :
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
if (cell.selected) {
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
[UIView transitionWithView:cell
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[cell setFrame:self.selectedCellDefaultFrame];
cell.transform = self.selectedCellDefaultTransform;
}
completion:^(BOOL finished) {
self.selectedCellDefaultFrame = CGRectZero;
[collectionView reloadItemsAtIndexPaths:#[indexPath]];
}];
return NO;
}
else {
return YES;
}
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[cell.superview bringSubviewToFront:cell];
self.selectedCellDefaultFrame = cell.frame;
self.selectedCellDefaultTransform = cell.transform;
[UIView transitionWithView:cell
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[cell setFrame:collectionView.bounds];
cell.transform = CGAffineTransformMakeRotation(0.0);
}
completion:^(BOOL finished) {}];
}
Different things here :
The bringSubviewToFront: message call is used to prevent the cell to animate behind the other cells
We use two properties declared in the controller : selectedCellDefaultFrameand selectedCellDefaultTransform to save the default state of the cell and reinitialize it when deselecting
When deselecting, we call the reloadItemsAtIndexPaths: method of UICollectionView to be sure that the reset of the position is totally complete
Let me know if you have any trouble with this.
Good luck,
I haven't tried the grow animation, but I think I can help with the UICollectionViewCell flip animation.
Try:
UICollectionViewCell* cell = [collectionView cellForItemAtIndexPath:indexPath];
[UIView animateWithDuration:1.0
delay:0
options:(UIViewAnimationOptionAllowUserInteraction)
animations:^
{
NSLog(#"starting animation");
[UIView transitionFromView:cell.contentView
toView:newView
duration:.5
options:UIViewAnimationOptionTransitionFlipFromRight
completion:nil];
}
completion:^(BOOL finished)
{
NSLog(#"animation end");
}
];
Hope that helps!
Related
I am trying to put animation in a UITableViewCell. Animation is that onClick table view cell change the frame of tableCell into tableview frame.
I have the following code:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 10;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell*cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell*cell = [tableView cellForRowAtIndexPath:indexPath];
if(cell.frame.size.height == tableView.bounds.size.height){
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}else{
cell.frame = tableView.bounds;
[[cell superview] bringSubviewToFront:cell];
}
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:0.5 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
}
It works fine but after change cell's frame I am able to tap another cells also which I don't want. How can I achieve this?
One of the possible solution would be to declare a variable that will hold an array of indexPath of expanded cell like this
// Will hold the indexPath of expanded cell
var expandedCell : [IndexPath] = []
Second thing would be adding and removing the the cell that are/aren't expanded and to do that you have to update your UITableView Delegate didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let index = expandedCell.firstIndex { (localIndexPath) -> Bool in
return indexPath == localIndexPath
}
if let index = index {
expandedCell.remove(at: index)
} else {
expandedCell.append(indexPath)
}
tableviewMessageList.reloadRows(at: [indexPath], with: .fade)
}
And in the end you have to add another UITableView Delegate heightForRowAt to return the height of the cell if the cell's indexPath is in array it will return the expanded size else return the normal size of your cell like this:-
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if expandedCell.contains(indexPath) {
return tableView.frame.height
}
return 200.0 //NormalHeight
}
Note: My answer is in Swift but the same principle will apply for Objective-C you just need to change the Syntax.
Overall, I think a better approach to this would be to use a separate view to display the cell details full screen, although you may have some restrictions that require you to do it this way. That being said, find my answer below.
This is what I gather your current problem is:
You'd like to show the cell expanded, and when in that expanded state touches are going through the cell view and hitting the table view behind it and re-triggering didSelectRowAtIndexPath: tableview delegate method (on other cells than the expanded one).
These are a couple of the possible solutions I see:
Add a tap gesture recognizer to the cell when it's expanded so it will absorb the touch, then remove the gesture recognizer once it's consumed.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapOnExpandedCell:)];
[cell addGestureRecognizer:tap];
cell.frame = tableView.bounds;
[[cell superview] bringSubviewToFront:cell];
UIView *bgView = [[UIView alloc] init];
bgView.backgroundColor = [UIColor purpleColor];
cell.selectedBackgroundView = bgView;
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:0.5 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
-(void)didTapOnExpandedCell:(UIGestureRecognizer *)recognizer {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:self.tableView.indexPathForSelectedRow];
// do whatever you were planning on doing when tapping on the
// expanded cell
[self.tableView reloadRowsAtIndexPaths:#[self.tableView.indexPathForSelectedRow] withRowAnimation:UITableViewRowAnimationNone];
[cell removeGestureRecognizer:recognizer];
}
Subclass UITableViewCell and override touchesBegan:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
R4NTableViewCell *cell = (R4NTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
cell.frame = tableView.bounds;
[[cell superview] bringSubviewToFront:cell];
UIView *bgView = [[UIView alloc] init];
bgView.backgroundColor = [UIColor purpleColor];
cell.selectedBackgroundView = bgView;
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:0.5 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
// in R4NTableViewCell.m implementation override touchesBegan
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// if we're not selected, don't intercept the touch so the tableview can handle it
// calling back to super will give the default tableview behavior and get our delegate callback
if (self.selected == NO) {
self.frameBeforeExpansion = self.frame;
[super touchesBegan:touches withEvent:event];
} else { // we're in the expanded state so intercept the touch
NSSet <UITouch *> *singleTouches = [[event allTouches] objectsPassingTest:^BOOL(UITouch * _Nonnull obj, BOOL * _Nonnull stop) {
return obj.tapCount == 1;
}];
if (singleTouches.count > 0) {
// the user single tapped our view
[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.7 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = self.frameBeforeExpansion;
[self.superview layoutIfNeeded];
} completion:^(BOOL finished) {
[self setSelected:NO];
self.backgroundView.backgroundColor = [UIColor greenColor];
}];
}
}
}
One additional thing I didn't really understand from your explanation (as the comments mentioned) is what you expect to happen when the user taps the expanded tableview cell.
Im trying to create a cool animation with my collection view selection. Basically I have a collection view of photo cells displayed. What I want to happen is have the cell pop out of its location, scale and move to the center of the screen where the user will be prompted to confirm the selection.
Here is my code so far, but currently the animation takes the cells original frame location, so if I scroll it doesnt take into account that the frame position is no longer the same.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
PhotoCell *cell = (PhotoCell *)[collectionView cellForItemAtIndexPath:indexPath];
collectionView.allowsSelection = NO;
[self createAnimationWithCell:cell];
}
- (void)createAnimationWithCell:(PhotoCell *)cell {
UIImageView *selectedImage = [[UIImageView alloc] initWithFrame:cell.bounds];
selectedImage.center = cell.center;
selectedImage.image = cell.imageView.image;
[self.view addSubview:selectedImage];
[UIView animateWithDuration:2.5 animations:^{
selectedImage.center = self.view.center;
} completion:^(BOOL finished) {
[selectedImage removeFromSuperview];
self.collectionView.allowsSelection = YES;
}];
}
I solved it myself:
For those who are experiencing the same problem, we have to take into account how much the collection view has scrolled (offset) and use that information. I created a variable called collectionViewOffset and gave it an initial value of 0.0
then used:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// getting the scroll offset
collectionViewOffset = scrollView.contentOffset.y;
NSLog(#"offset: %f", collectionViewOffset);
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
PhotoCell *cell = (PhotoCell *)[collectionView cellForItemAtIndexPath:indexPath];
//collectionView.allowsSelection = NO;
//collectionView.scrollEnabled = NO;
[cell.superview bringSubviewToFront:cell];
[UIView animateWithDuration:2.0 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:.2 options:UIViewAnimationOptionCurveLinear animations:^{
cell.center = CGPointMake(self.view.vWidth/2, self.bannerView.vBottomEdge + collectionViewOffset + 40);
} completion:^(BOOL finished) {
}];
}
I am trying to make custom cell highlighted animation...but it does not work. Any thoughts how can I animate .alpa change?
ViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"myID";
TableViewCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
cell.selectedImg.frame = cell.contentView.frame;
cell.selectedImg.image = [ViewController imageFromColor:[UIColor colorWithRed:0.55 green:0.95 blue:0.68 alpha:0.0]];
cell.selectedImg.hidden = NO;
return cell;
}
TableViewCustomCell.m
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
if (highlighted) {
[UIView animateWithDuration:0.2 animations:^(void) {
self.selectedImg.alpha = 0.5;}];
}
else {
[UIView animateWithDuration:0.2 animations:^(void) {
self.selectedImg.alpha = 0.0;}];
}
}
P.S. I will have a lot of UITableViews and they all will have the same cell style, so Should I place cell.selectedImg code somewhere in TableViewCustomCell.m? If yes, where is the right place?
Try to replace the code in
(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
in TableViewCell.m with the following code:
[UIView animateWithDuration:0.2
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
if (highlighted) {
self.selectedImg.alpha = 0.5;
}
else {
self.selectedImg.alpha = 0;
}
}
completion:^(BOOL finished){
if (highlighted) {
self.selectedImg.alpha = 0;
}
else {
self.selectedImg.alpha = 0.5;
}
}];
I have a Horizontal UICollectionView with n number of records. Each cell has an UIImageView which needs to animate the Fadein Fadeout with some TimerInterval. So, i have planned to implement that reload my UICollectionView using NSTimer My code snippet,
....
....
if (collectionTimer) {
[collectionTimer invalidate];
collectionTimer = nil;
}
collectionTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(reloadCollection) userInfo:nil repeats:YES];
....
....
reloadCollection Method
-(void)reloadCollection{
[_collectionView reloadData];
}
cellForItemAtIndexPath Method
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
....
....
/* Basic Animation working fine*/
[UIView animateKeyframesWithDuration:1.0 delay:1.0 options:UIViewKeyframeAnimationOptionAutoreverse animations:^{
cell.articleImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#.jpg", [images objectAtIndex:i]]];
} completion:nil];
/* Not working */
/* [UIView transitionWithView:cell.articleImage duration:1.0f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
cell.articleImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#.jpg", [images objectAtIndex:i]]];
} completion:nil]; */
....
....
}
The basic animation works fine. But, whenever i am trying to implement UIViewAnimationOptionTransitionCrossDissolve with transitionWithView method. It will showing the animation, but immediately one more image loaded because of reloadData calling. How to fix this? How to animate only the images in UICollectionView
Any Idea appreciated!
My animation work like this:
cell.articleImage.alpha = 0.0;
[UIView animateWithDuration:0.3f animations:^{
[cell.articleImage setAlpha:1.0];
}];
Try to add the animation for visible cells without reloading the collectionView:
NSArray* visibleCells = [collectionView visibleCells];
[visibleCells enumerateObjectsUsingBlock:^(UICollectionViewCell* cell, NSUInteger idx, BOOL *stop) {
//TODO: add animation;
}];
Update
To animate new cells, you should start animation after adding cell to window. In cell you can override method didMoveToWindow:
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (self.window && self.shouldStartAnimation)
{
[self startAnimation];
}
}
And in ViewController these methods:
- (void)animateVisibleCells
{
NSArray* visibleCells = [self.collectionView visibleCells];
[visibleCells enumerateObjectsUsingBlock:^(BTCollectionViewCell* cell, NSUInteger idx, BOOL *stop) {
[cell startAnimation];
}];
}
#define ANIMATION_DURATION (0.3)
- (void)animateCells
{
self.shouldAnimationStart = YES;
[self animateVisibleCells];
[NSTimer scheduledTimerWithTimeInterval:ANIMATION_DURATION target:self selector:#selector(endAnimateCells) userInfo:nil repeats:NO];
}
- (void)endAnimateCells
{
self.shouldAnimationStart = NO;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
((BTCollectionViewCell*)cell).shouldStartAnimation = self.shouldAnimationStart;
}
But problem with synchronization of the animation continues to be (new animations will starts from 0 time). If you know method to start animation from concrete time, you can use it method in willDisplayCell
Well i guess that in your method cellForItemAtIndexPath you are not configuring animation right.
please use this.
[UIView animateKeyframesWithDuration:1.0 delay:0.1 options:UIViewKeyframeAnimationOptionAutoreverse animations:^{
cell.articleImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#.jpg", [images objectAtIndex:i]]];
} completion:nil];
You were not assigning the delay properly to let animation work.
Hope it works for you now. Thanks
I'm trying to make a navigation-site for my app with an UICollectionView. As a user clicks on one of the cells, the ViewController should push another one with a zooming animation of the Image of the cell.
Everything is working fine and the next view is shown but here is the problem: when I called the next view and then navigate back to my UICollectionView and click on my cell again, the animation isn't shown anymore.
So I think that the ViewController doesn't call the
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{}
function anymore and kind of remembers the seque. But how do I tell him to do the animation again?
Here is the code for the segue:
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
CostumCollectionCell *theCell =(CostumCollectionCell*)[self.theCollectionView cellForItemAtIndexPath:indexPath];
UIImageView *overlayer = [[UIImageView alloc]initWithImage:theCell.theImageView.image];
overlayer.frame=theCell.frame;
[overlayer setContentMode:UIViewContentModeScaleAspectFill];
[self.theCollectionView addSubview:overlayer];
[UIView animateWithDuration:0.75 animations:^{
overlayer.frame=CGRectMake(theCell.frame.origin.x-theCell.frame.size.width/2, theCell.frame.origin.y-theCell.frame.size.height/2, theCell.frame.size.width*2, theCell.frame.size.height*2);
} completion:^(BOOL finished) {
switch (indexPath.row) {
case 0:
[self.navigationController pushViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"auditionView"] animated:YES];
[overlayer removeFromSuperview];
break;
default:
break;
}
}];
}
updated code:
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
CostumCollectionCell *theCell =(CostumCollectionCell*)[self.theCollectionView cellForItemAtIndexPath:indexPath];
[self.overlayer setImage:theCell.theImageView.image];
self.overlayer.frame=theCell.frame;
[self.overlayer setHidden:NO];
[UIView animateWithDuration:0.75 delay:0.0 usingSpringWithDamping:0.5 initialSpringVelocity:5.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.overlayer.frame=CGRectMake(theCell.frame.origin.x-theCell.frame.size.width/2, theCell.frame.origin.y-theCell.frame.size.height/2, theCell.frame.size.width*2, theCell.frame.size.height*2);
} completion:^(BOOL finished) {
if(finished){
switch (indexPath.row) {
case 0:
[self performSegueWithIdentifier:#"auditionSegue" sender:self];
[self.overlayer setHidden:YES];
break;
default:
break;
}
}
}];
}