I have a UICollectionView. I want a behaviour where when the user touches a cell, it scales down, as if it is getting pushed down slightly. I've accomplished this by using the UICollectionViewDelegate methods:
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[self scaleDownCell:cell];
}
- (void) collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[self scaleUpCell:cell];
}
My problem is that sometimes my cell will start to scale down, and then suddenly go back to full size, without performing the scale up animation, creating jerky effect. I have checked with breakpoints that it scales up before my scale up method is called. I am trying to figure out why.
The scale functions are as follows:
+ (void)scaleDownCell:(UICollectionViewCell *)cell
{
CGAffineTransform transform = CGAffineTransformMakeScale(0.95, 0.95);
[UIView animateWithDuration:SCALE_DOWN_ANIMATION_DURATION
delay:0.0
usingSpringWithDamping:SCALE_DOWN_SPRING_DAMPING
initialSpringVelocity:SCALE_DOWN_SPRING_VELOCITY
options:0
animations:^{
cell.transform = transform;
}
completion:^(BOOL finished) {
}];
}
+ (void)scaleUpCell:(UBCollectionViewCell *)cell
{
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 1.0);
[UIView animateWithDuration:SCALE_UP_ANIMATION_DURATION
delay:0.3
usingSpringWithDamping:SCALE_UP_SPRING_DAMPING
initialSpringVelocity:SCALE_UP_SPRING_VELOCITY
options:0
animations:^{
cell.transform = transform;
}
completion:^(BOOL finished) {
}];
}
EDIT: I basically want to replace highlighting in my cells with this scaling behaviour, it serves the exact same purpose and I want it to happen in exactly the same situation that the cell would be highlighted, so it seemed like an appropriate place to apply my transformation.
Regardless, I had first tried overriding the UIResponder touch methods in the cell subclass, but was observing the same behaviour. The sudden scale up happens even if I delete the scale up method so that it is never called, so my scale down is not being cancelled by the scale up animation. I can see through log statements that my scale down is called once, and the animation completion block is getting called with finsished == YES. The cell is getting scaled back up BEFORE the completion block is called (I have set the animation duration to be extra long to help in debugging). This seems to happen when I touch on the cell and then quickly scroll, so I guess the scrolling has something to do with it.
The problem is that you are hijacking the notion of "highlighting" for something that it isn't. Highlighting is a complicated and confusing business. In the course of being tapped and selected, the cell highlights and unhighlights more than once. Thus it is the wrong thing to respond to.
If what you are trying to detect is a touch, then you should respond to touch. If a gesture recognizer won't do, then use a cell subclass of your own so that you can implement UITouch detection directly.
One more thing to keep in mind is that, the way you've written this, if the scale up happens while the scale down is in progress, it will just kill it dead.
EDIT - One final thought - in your edit, you make a comment about scrolling having something to do with this. That makes sense. Think how a collection view layout works - it is responsible for the attributes of each cell, including its transform (https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewLayoutAttributes_class/Reference/Reference.html). So naturally if you change the transform of the cell yourself, and you don't tell the layout, and the layout takes charge, it will cancel your transform.
Related
I have a UITableView displaying cells. I am using Auto Layout. At some point, I add cells to a section of my tableView and I need to animate a subview of each cells as soon as the cells are visible.
I wrote the following method to animate the content of my cell:
- (void)animateWithPercentage:(NSNumber *)percentage
{
self.progressViewWidthConstraint.constant = [percentage intValue];
[self.progressView setNeedsUpdateConstraints];
[UIView animateWithDuration:0.6f animations:^{
[self.progressView layoutIfNeeded];
}];
}
The method works as expected but I don't know when to call it.
If I call it when - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath is called, I end up animating the whole layout of my cell because the cell did not layout itself before.
I also tried calling it in layoutSubviews but the animation is skipped. I guess at this point it is too early to animate anything.
Any idea where my method should be called?
I've gotten this kind of animation to work by calling performSelector:withObject:afterDelay: in either awakeFromNib or didMoveToSuperview in the custom cell's class. It works even with a delay of 0, but it doesn't work if you just use performSelector. I've noticed that with this method, if some of the cells are off screen, they don't update until they are scrolled on, and the scrolling animation ends. You can get the animation to start as soon as the cell scrolls on screen, even while the scrolling animation is still in progress by using dispatch_async instead.
-(void)awakeFromNib {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:2 animations:^{
self.rightCon.constant = 150;
[self.contentView layoutIfNeeded];
} completion:nil];
});
}
Try to put your method in -(void)didMoveToSuperview
This video shows how cells with views extending out of the cell area get clipped momentarily when new cells are being inserted:
https://dl.dropboxusercontent.com/u/22105205/CellClipping.mov
This simple project clearly shows the problem and can be used for quick prototyping:
https://github.com/AndresCanella/iOSInsertCellClippingExample.git
This clipping only occurs when the table is mutated.
Cells are clear.
Cells display correctly when not mutating.
Possibly some sort of optimization that only uses pix from within the cell area for animation?
Everything is setup correctly, stable, and works as expected, we are not even using specific cell data for this example:
[tableView beginUpdates];
self.cells++;
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];
[tableView endUpdates];
Update:
Response from DTS:
I’m afraid you are not going to be able to directly affect the insertion animation behavior when calling “insertRowsAtIndexPaths”, regardless of the kind of “UITableViewRowAnimation” you are using. Cell content conventionally don’t overlap like that. UITableView is simply honoring the cell’s bounds (not it’s extended or overlapping content), when performing its animation block of each cell.
My comment:
I've been told by DTS that things can be done plenty of times and I've always found a workaround. So now I'm looking for a work around.
Apple Bug Report # 17986466
It looks to me like the views you wish to not be clipped exceed the bounds of the cells themselves. To me, that says that these should be subviews of the table view instead of the cell.
If that is indeed the case, you may wish to use a plain UIScrollView instead of a UITableView, and animate the the views below the one being inserted downwards.
In my experience you should try to keep the cells with a non-clear/transparent background and set to clip subviews if you want to avoid weird layouts and animation glitches.
The way the cells view hierarchy is set inside the table view and how animations are made is internal to Apple implementation and prone to change without notice in future releases.
Table views are good at displaying tons of rows and reusing views, things that maybe your view is not really using. If your desired layout does not require several screens of scrollable content maybe you should try to create your own custom UIScrollView-based view or search for one among the many open source libraries. You would have complete control of animations and add custom behaviors.
I understand that this is a complete hack - but it does fix the clipping.
For granular animations - check out PRTWeen.
https://github.com/jdp-global/MWDatePicker
I guess you considered toggling 2 transparent background images (red and green) (640px x100px) on current cell and previous cell? It may work using a fade in animation on insert.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.cells++;
[self.tb reloadData];
[self performSelector:#selector(fancyAnimate:) withObject:indexPath afterDelay:0];
}
-(void)fancyAnimate:(NSIndexPath*)path{
NSIndexPath *idx = [NSIndexPath indexPathForRow:path.row inSection:path.section] ;
UITableViewCell *cell = (UITableViewCell*)[self.tb cellForRowAtIndexPath:idx];
CGRect frame = cell.frame;
frame.origin.x = 320;
cell.frame = frame;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
frame.origin.x = 0;
cell.frame = frame;
[UIView commitAnimations];
}
So to make it simple I'm trying to have the same view as in iMessage: a reversed UITableView.
I have a rotated UITableView :
self.tableView.transform = CGAffineTransformMakeRotation(-M_PI);
Each UITableViewCell is also rotated to appear the right way:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.transform = CGAffineTransformMakeRotation(M_PI);
return cell;
}
When the keyboard appears, the frame of my UITableView is changed, so that the bottom of my UITableView follows the top of the keyboard. Same thing when the keyboard hides. To do this I use an animation.
My problem is that when the keyboards hide, the frame of the UITableView increases, and some new cells are displayed. As they are displayed, the delegate calls tableView:cellForRowAtIndexPath: and the animation also applies on the
cell.transform = CGAffineTransformMakeRotation(M_PI);
So I see my new cells rotating!
Is there any way I could avoid the animation on the rotation?
You need to disable animations if you don't want the setting of animatable properties to be animated:
BOOL wasEnabled = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
cell.transform = CGAffineTransformMakeRotation(M_PI);
[UIView setAnimationsEnabled:wasEnabled];
On iOS 7, you can use [UIView performWithoutAnimation:...].
Also, I would avoid doing
self.tableView.transform = CGAffineTransformMakeRotation(-M_PI);
Last I checked (iOS 5 or 6?), this would cause the cell sizes to be incorrect, as if UITableView used its frame's width to decide how "wide" cells should be. Stick it in a view and set the transform of that view instead (or check that it does the right thing on each major OS version you need to support).
Hey mate try to maintain a bool variable when you dont need the animation set the bool variable to false during some editing or any other event. So put the condition of bool variable if it is True then only go for animation.
regards and have a nice year ahead
I have a custom animation in:
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
of the UITableViewCell, something like:
[UIView animateWithDuration:0.15 animations:^{
self.center = CGPointMake(self.center.x + 110, self.center.y);
} completion:^(BOOL finished) {
isShifted = finished;
}];
The idea here is to shift the cell when the touch is down. Then if the cell is selected (no scrolling happened or cancel events) I proceed to another animation of the cell and transition to a new view.
The problem here is with an animation putting the cell back into its original position on setHighlighted:NO method call. Visually it looks like upon the selection the cell shifts (touch down), then starts going back (the system removes the highlight) and then starts the transition (the system calls setSelected:YES).
As far as I can see upon the selection of the cell in the table the following happens:
setHighlighted:YES
setHighlighted:NO
setSelected:YES
Is there any elegant solution to skip the call 2 and avoid the back-shifting-animation without introducing the timer that will check the selection?
You can think of using the delegate method tableView:shouldHighlightRowAtIndexPath:
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
// The cell has been touched, perform custom animation here
return NO;
}
setHighlight: will never be called on any cell, but you still have an entry point to perform the custom animation.
If you have a delegate for your UITableView (presumably your own view controller), you can catch the selection happening via something like "tableView: willSelectRowAtIndexPath:.
You can then do something "special" to your cell (such as set a property that forces the cell to do what you want in your animation) while the cell is selected.
And restore it back to what it was before when the cell is deselected via "tableView:willDeselectRowAtIndexPath:".
You also have the same type of delegate methods available for highlighting, e.g. "tableView:shouldHighlightRowAtIndexPath:"
I'm writing a custom UITableView with custom UITableViewCells. An unfortunate side effect of this, as many devs have found out, is that when the table starts editing, the content (UILabel in my case) isn't automatically moved. So, here's the code I've written to deal with it located in the UITableViewCell custom subclass:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
if (animated) {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect titleFrame = self.titleLabel.frame;
if (editing) {
[self.titleLabel setFrame:CGRectMake(15, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
} else {
[self.titleLabel setFrame:CGRectMake(25, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
}
}completion:nil];
}
Unfortunately, I can't seem to figure out the correct options to use as no combination I've tried has worked right. Without UIViewAnimationOptionBeginFromCurrentState the UILabel shifts to the far left before beginning the animation, which is not desirable. And without UIViewAnimationOptionBeginFromCurrentState the UILabel performs the animation fine, but then has a small rubberband effect on the end.
Any ideas as to what's going on?
Thanks.
EDIT:
I've figured out whats happening! Not sure how to fix it, but now I now exactly what the question is!
Firstly, I had to uncheck 'use Autolayout' to get the cell to move automatically.
The labels initial x value is 25 when not editing. When editing, if the animation takes effect without moving the label myself, it moves to 25 points right of the left accessory item. The reason the first line there says 15:
if (editing) {
[self.titleLabel setFrame:CGRectMake(15 ...
is because that 25 point gap is pretty wide, and doesn't look right. So the animation, with the code in place, moves the label to the 25 mark, then back to the 15 mark, creating that rubber banding effect.
In trying to remedy this problem, I've tried all sorts of things like moving the [super setEditing:...] call below the code, always setting the animated argument of that call to NO... on and on and no luck. The solution is to somehow make the label animate over a shorter distance than the x value, or (as I was originally attempting to do) create the animation locally to send it to a specific x value and have it stay there.
My instincts tell me that it's a problem with the chain reaction of [setEditing ] calls that happens with the UITableView where the animation of the contentView (possibly) is happening before my code block, thus moving the label over and then my code executes, moving it back.
Note: the behavior occurs whether or not the label is a subview of contentView.
You haven't shown it in the question but it looks like you're adding your titleLabel directly to the cell.
Add your custom content to the cell's contentView instead, with appropriate resizing masks, and it will resize for you during the normal transition to editing mode with no need to override any methods. That's how it works with the standard label and image view. The content view is resized to allow the editing accessories to be shown, and the content view's subviews resize automatically.
try following code
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[self beginUpdates];
if (animated) {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect titleFrame = self.titleLabel.frame;
if (editing) {
[self.titleLabel setFrame:CGRectMake(15, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
} else {
[self.titleLabel setFrame:CGRectMake(25, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
}
}completion:nil];
[self endUpdates];
}