UITableViewCell Position Off After Animation - ios

I have an animation going on in -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *). The cells essentially rotate in (code below). They do this every time they load onto the screen. The cells that rotate in initially (that is, the ones visible on screen at the beginning) look like the "Before" snapshot below. Small (normal) indentation. After scrolling down or around, new cells are animated onto the screen, but they look like the "After" shot. They're off by a few pixels. Once I scroll up again, the upper cells are reloaded and also have the larger indentation. All of which would be fine, except depending on scroll behavior, sometimes some cells are indented and others aren't and it looks awful. I tried resetting the indent property of UITableViewCell but it didn't do anything. Any ideas what could cause this odd behavior? Perhaps a property I'm not aware of I should be setting? Thanks!
Before:
After:
//Animation Code Borrowed From http://www.thinkandbuild.it/animating-uitableview-cells/
//1. Setup the CATransform3D structure
CATransform3D rotation;
rotation = CATransform3DMakeRotation( (90.0*M_PI)/180, 0.0, 0.7, 0.4);
rotation.m34 = 1.0/ -600;
//2. Define the initial state (Before the animation)
cell.layer.shadowColor = [[UIColor blackColor]CGColor];
cell.layer.shadowOffset = CGSizeMake(10, 10);
cell.alpha = 0;
cell.layer.transform = rotation;
cell.layer.anchorPoint = CGPointMake(0, 0.5);
//3. Define the final state (After the animation) and commit the animation
[UIView beginAnimations:#"rotation" context:NULL];
[UIView setAnimationDuration:0.8];
cell.layer.transform = CATransform3DIdentity;
cell.alpha = 1;
cell.layer.shadowOffset = CGSizeMake(0, 0);
[UIView commitAnimations];
EDIT
After going to the original site's source code again, I noticed an update to the code and I applied it:
if(cell.layer.position.x != 0){
cell.layer.position = CGPointMake(0, cell.layer.position.y);
}
However, it didn't seem to fix this issue, and I think it was a fix for another positioning issue this animation had.

i am not sure just give it a try,
just by forcing the layer position to origin, see below code
//1. Setup the CATransform3D structure
CATransform3D rotation;
rotation = CATransform3DMakeRotation( (90.0*M_PI)/180, 0.0, 0.7, 0.4);
rotation.m34 = 1.0/ -600;
//2. Define the initial state (Before the animation)
cell.layer.shadowColor = [[UIColor blackColor]CGColor];
cell.layer.shadowOffset = CGSizeMake(10, 10);
cell.alpha = 0;
cell.layer.transform = rotation;
cell.layer.anchorPoint = CGPointMake(0, 0.5);
if(cell.layer.position.x != 0)
{
cell.layer.position = CGPointMake(0, cell.layer.position.y);
}
//add this and try
CGPoint point = cell.layer.position;
point.x = 0.0f; //setting the position back to original (for your case)
cell.layer.position = point;
//4. Define the final state (After the animation) and commit the animation
[UIView beginAnimations:#"rotation" context:NULL];
[UIView setAnimationDuration:0.8];
cell.layer.transform = CATransform3DIdentity;
cell.alpha = 1;
cell.layer.shadowOffset = CGSizeMake(0, 0);
[UIView commitAnimations];

Related

What is the initial x coordinate of a UITableViewCell?

In a UITableViewController, this function logs into the console the x coordinate of the tableview cells
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
//1. Setup the CATransform3D structure
CATransform3D rotation;
rotation = CATransform3DMakeRotation( (45.0*M_PI)/180, 0.0, 0.7, 0.4);
rotation.m34 = 1.0/ -600;
//2. Define the initial state (Before the animation)
cell.layer.shadowColor = [[UIColor blackColor]CGColor];
cell.layer.shadowOffset = CGSizeMake(10, 10);
cell.alpha = 0;
cell.layer.transform = rotation;
cell.layer.anchorPoint = CGPointMake(0, 0.5);
//4. Define the final state (After the animation) and commit the animation
[UIView beginAnimations:#"rotation" context:NULL];
[UIView setAnimationDuration:0.3];
cell.layer.transform = CATransform3DIdentity;
cell.alpha = 1;
cell.layer.shadowOffset = CGSizeMake(0, 0);
[UIView commitAnimations];
NSLog(#"X%d= %d,Y%d=%d",(int)indexPath.row ,(int)cell.layer.position.x,(int)indexPath.row,(int)cell.layer.position.y);
}
it logs for the cells that are initially displayed (cell 0 to 12)
X0= 160,Y0=25
X1= 160,Y1=75
X2= 160,Y2=125
X3= 160,Y3=175
X4= 160,Y4=225
X5= 160,Y5=275
X6= 160,Y6=325
X7= 160,Y7=375
X8= 160,Y8=425
X9= 160,Y9=475
X10= 160,Y10=525
X11= 160,Y11=575
X12= 160,Y12=625
But after scrolling the tableview, it logs
X13= 0,Y13=675
X14= 0,Y14=725
X15= 0,Y15=775
X16= 0,Y16=825
and only cell 13's view is offset by 160 in the simulator
This could be fixed by adding to the previous method
if(cell.layer.position.x != 0){
cell.layer.position = CGPointMake(0, cell.layer.position.y);
}
What I am asking is why initially the x coordinate = 160 ???
The position of a layer denotes its center position, and not the top-left corner. you changed that behaviour through manipulation of the anchorPoint property. Insert the following line after the line that contains anchorPoint:
cell.layer.position = CGPointMake(0, cell.layer.position.y);
Changing the anchorPoint also changes the frame. To keep the frame the same, you have to do the opposite movement with the position property.
(To get the position of the top-left corner, you would use cell.frame.origin instead.)
See also: CALayer reference

Adding a new UITableViewRow pushes the existing cells down too quickly

I am trying to create UITableCellView animation like Peek Calendar. As can be seen here:
I am using UITableView and the following code for animation
//Animaiton for the table view cells to appear
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if([cell.reuseIdentifier isEqualToString:#"HolidayCell"]) {
int x = (indexPath.row-self.selectedMonth) %2;
//1. Setup the CATransform3D structure
CATransform3D rotation;
CATransform3D position;
position = CATransform3DMakeTranslation(0, -36, 0) ;
//rotation.m33 = 12;
UILabel *shadow = (UILabel*)[cell.contentView viewWithTag:2];
//2. Define the initial state (Before the animation)
cell.alpha = 1;
shadow.alpha = 0.75;
if(x==1) {
rotation = CATransform3DMakeRotation((-90.0*M_PI)/180, 1.5, 0.0, 0.0);
rotation.m34 = 0.0059;
cell.layer.anchorPoint = CGPointMake(0.5, 0.0);
}
else
{
rotation = CATransform3DMakeRotation((90.0*M_PI)/180, 1.5, 0.0, 0.0);
rotation.m34 = 0.0059;
cell.layer.anchorPoint = CGPointMake(0.5, 1.0);
}
cell.layer.transform = CATransform3DConcat(position, rotation);
//3. Define the final state (After the animation) and commit the animation
[UIView beginAnimations:#"rotation" context:NULL];
[UIView setAnimationDuration:2.3];
//[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:cell cache:YES];
cell.layer.transform = CATransform3DIdentity;
cell.alpha = 1;
shadow.alpha = 0;
//cell.layer.shadowOff
1. List item
set = CGSizeMake(0, 0);
[UIView commitAnimations];
}
}
Now I don't have any issue in particular with the animation. The cells are rotating just fine. However my issue is that when a cell i inserted even while rotated it is still taking up space as if it were fully open.
As you can see the cells are rotating within their "virtual boxes" however I want the cells to stick together as depicted in this image:
Does anyone have any idea on how to do this?
You are not transforming the frame, only the layer. The layer is only the visual representation of the cell, and does not define size or position of the cell.
I believe, in a tableView, the frame of a cell is defined first by the heightForRowAtIndexPath: method, but you should be able to change and animate it later using the "frame" property. Try setting it to zero, and animate to the final height when the cell appears.
Code, that finally gave the solution: https://www.cocoacontrols.com/controls/jtgesturebasedtableviewdemo
Here is some code, that actually works (tested it).
Put it into the
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath method, and comment out the other code for testing
CGRect cellFrame = cell.frame;
//replace with the y coordinate of the point, where the cell(s) get inserted
float insertPoint = 0;
//replace with the duration you wish
float animationDuration = 5;
[cell setFrame:CGRectMake(cellFrame.origin.x, insertPoint, cellFrame.size.width, 0)];
[UIView animateWithDuration:animationDuration animations:^{
[cell setFrame:cellFrame];
}];
That may not be exactly what you want, but it should give you an idea...

CATransform3DRotate effects gone after applying anchor point

EDIT with correct observation.
I used the following two snippets to rotate an UIImageView. After the stage1, I got the desired 3D effect: the view rotate (3D effect) and stretched out (larger size). On stage2, I am trying to swing the rightView with the hinge on the right.
If I applied the line that changes the anchor point, the view swings correctly (hinge on right) with one major issue: The view's size got restored back to original size (smaller) while the (rotation) 3D effect is still intact and then the swinging occurs.
Any suggestions?
rightView.layer.anchorPoint = CGPointMake(1.0, 0.5);
-(void)stage1
{
[UIView animateWithDuration:1.5 animations:^{
rightView.transform = CGAffineTransformMakeTranslation(0,0); //rightView i UIImageView
CATransform3D _3Dt = CATransform3DIdentity;
_3Dt =CATransform3DMakeRotation(3.141f/42.0f,0.0f,-1.0f,0.0f);
_3Dt.m34 = -0.001f;
_3Dt.m14 = -0.0015f;
rightView.layer.transform = _3Dt;
} completion:^(BOOL finished){
if (finished) {
NSLog(#"finished..");
}
}];
}
-(void)Stage2
{
rightView.layer.anchorPoint = CGPointMake(1.0, 0.5); //issue?
rightView.center = CGPointMake(1012, 384);
[UIView animateWithDuration:1.75 animations:^{
CATransform3D rightTransform = rightView.layer.transform;
rightTransform.m34 = 1.0f/500;
rightTransform = CATransform3DRotate(rightTransform, M_PI_2, 0, 1, 0);
rightView.layer.transform = rightTransform;
} completion:^(BOOL finished) {
}];
}
You need to add the "options" to your animate with duration and add options:UIViewAnimationOptionBeginFromCurrentState
Otherwise it starts from the beginning again.
So your stage 2 would look like this:
-(void)Stage2
{
rightView.layer.anchorPoint = CGPointMake(1.0, 0.5); //issue?
rightView.center = CGPointMake(1012, 384);
[UIView animateWithDuration:1.75
delay:0.00
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
CATransform3D rightTransform = rightView.layer.transform;
rightTransform.m34 = 1.0f/500;
rightTransform = CATransform3DRotate(rightTransform, M_PI_2, 0, 1, 0);
rightView.layer.transform = rightTransform;
} completion:^(BOOL finished) {
}];
}

Smooth scaling of vector graphics in UIView

So I have subclassed UIView and added some drawing code. I am scaling the resulting view up and down.
I would like this view to be resolution independent so that it is legible at any size, and I won't need to manage multiple images etc. etc.
As a test I made up a bit of drawing code that looks like this.
It creates concentric ovals that fit within whatever frame size the UIView has.
Actually the outside ring is a little smaller than the frame so it isn't clipped. Fine for this. The actual graphic will be more complex and will contain text which must be readable at small sizes and things of that nature.
- (void)drawRect:(CGRect)rect
{
UIColor* color = [UIColor colorWithRed: 0.833 green: 0.833 blue: 0.833 alpha: 1];
float width = self.bounds.size.width;
float height = self.bounds.size.height;
float scalePercent = 0.8;
for(int i = 0; i < 10; i++){
width = width * scalePercent;
height = height * scalePercent;
float x = (self.bounds.size.width - width) / 2;
float y = (self.bounds.size.height - height) / 2;
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(x, y, width, height)];
[color setStroke];
ovalPath.lineWidth = 2;
[ovalPath stroke];
}
}
Now here's the scaling:
- (void) makeBig{
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform = CGAffineTransformMakeScale(2, 2);
}
completion:^(BOOL finished){
}];
}
When you run this the view zooms up, but it is pixelated. It's pixelated because the view has doubled in size but it's resolution has not changed.
So, here's how not to do it.
- (void) makeBig{
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform = CGAffineTransformMakeScale(2, 2);
}
completion:^(BOOL finished){
CGRect targetFrame = self.frame;
self.transform = CGAffineTransformIdentity;
self.frame = targetFrame;
[self setNeedsDisplay];
}];
}
This works, but the fix is visible at the end of the animation when the resolution snaps back to screen resolution.
I could try pre-scaling the view up and pre-drawing at the final size then scaling it down and then running the animation to scale it back up again, but for various reasons that I can think of that sounds totally stupid. I suppose I could be wrong and it's the brightest idea since fire. I kind of doubt it though.
So how is a smooth scale of vector content done best?
View-based animation is really handy, but if I'm not mistaken it uses CABasicAnimation, which only uses a single keyframe. It'll be a little more code, but if you use a CAKeyframeAnimation instead, Core Animation will redraw the contents of the animated layer for each keyframe (you get to specify when those occur), so you can avoid the appearance of pixelation.

How to animate UIImageViews like hatch doors opening

I'm trying to create an animation that would look like 2 french doors (or 2 hatch doors) opening towards the user.
I tried using the built in UIViewAnimationOptionTransitionFlipFromRight transition, but the origin of the transition seems to be the center of the UIImageView rather than the left edge. Basically I have 2 UIImageViews that each fill have the screen. I would like the animation to look like the UIImageViews are lifting from the center of the screen to the edges.
[UIView transitionWithView:leftView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^ { leftView.alpha = 0; }
completion:^(BOOL finished) {
[leftView removeFromSuperview];
}];
Has anyone done something like this before? Any help would be awesome!
UPDATE:
Working code thanks to Nick Lockwood
leftView.layer.anchorPoint = CGPointMake(0, 0.5); // hinge around the left edge
leftView.frame = CGRectMake(0, 0, 160, 460); //reset view position
rightView.layer.anchorPoint = CGPointMake(1.0, 0.5); //hinge around the right edge
rightView.frame = CGRectMake(160, 0, 160, 460); //reset view position
[UIView animateWithDuration:0.75 animations:^{
CATransform3D leftTransform = CATransform3DIdentity;
leftTransform.m34 = -1.0f/500; //dark magic to set the 3D perspective
leftTransform = CATransform3DRotate(leftTransform, -M_PI_2, 0, 1, 0);
leftView.layer.transform = leftTransform;
CATransform3D rightTransform = CATransform3DIdentity;
rightTransform.m34 = -1.0f/500; //dark magic to set the 3D perspective
rightTransform = CATransform3DRotate(rightTransform, M_PI_2, 0, 1, 0);
rightView.layer.transform = rightTransform;
}];
First add the QuartzCore library to your project and #import <QuartzCore/QuartzCore.h>
Every view has a layer property with sub-properties that are animatable. This is where you'll find all the really cool stuff when it comes to animation capabilities (I suggest reading up on the CALayer class properties you can set - it will blow your mind - dynamic soft drop shadows on any view?)
Anyway, back on topic. To rotate your doors open in 3D, first position them as if they were closed, so with each door filling half the screen.
Now set their view.layer.anchorPoint properties as follows
leftDoorView.layer.anchorPoint = CGPoint(0, 0.5); // hinge around the left edge
rightDoorView.layer.anchorPoint = CGPoint(1.0, 0.5); // hinge around the right edge
Now apply the following animation
[UIView animateWithDuration:0.5 animations:^{
CATransform3D leftTransform = CATransform3DIdentity;
leftTransform.m34 = -1.0f/500; //dark magic to set the 3D perspective
leftTransform = CATransform3DRotate(leftTransform, M_PI_2, 0, 1, 0); //rotate 90 degrees about the Y axis
leftDoorView.layer.transform = leftTransform;
//do the same thing but mirrored for the right door, that probably just means using -M_PI_2 for the angle. If you don't know what PI is, Google "radians"
}];
And that should do it.
DISCLAIMER: I've not actually tested this, so the angles may be backwards, and the perspective may be screwy, etc. but it should be a good start at least.
UPDATE: Curiosity got the better of me. Here is fully working code (this assumes that the left and right doors are laid out in the closed position in the nib file):
- (void)viewDidLoad
{
[super viewDidLoad];
leftDoorView.layer.anchorPoint = CGPointMake(0, 0.5); // hinge around the left edge
leftDoorView.center = CGPointMake(0.0, self.view.bounds.size.height/2.0); //compensate for anchor offset
rightDoorView.layer.anchorPoint = CGPointMake(1.0, 0.5); // hinge around the right edge
rightDoorView.center = CGPointMake(self.view.bounds.size.width,self.view.bounds.size.height/2.0); //compensate for anchor offset
}
- (IBAction)open
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0f/500;
leftDoorView.layer.transform = transform;
rightDoorView.layer.transform = transform;
[UIView animateWithDuration:0.5 animations:^{
leftDoorView.layer.transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
rightDoorView.layer.transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
}];
}
- (IBAction)close
{
[UIView animateWithDuration:0.5 animations:^{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0f/500;
leftDoorView.layer.transform = transform;
rightDoorView.layer.transform = transform;
}];
}

Resources