This has been asked before, but there is no sample code to be found on stackoverflow and no proper solutions as far as I'm aware. I have a solution, but it looks crap. I'd be grateful for any input and modifications to get a more realistic 3D door open / book cover animation.
Aim is to have an animation between UIViewControllers so that you get the effect as if you were opening a door or a book cover. ViewController 2 is static and in the background. Above it you place ViewController 1 which covers ViewController 2. The animation will open ViewController 1 (like a hardcover book) and reveal ViewController 2 (your first page so to say, or whatever is behind the door).
First thing to note is that you can't do this with an UINavigationController as it is difficult to overwrite the custom animations. A proper tutorial on how to set up our two ViewControllers can be found here: ViewController Animations
So once everything is setup, here is my custom animation which looks crap. It basically looks as if you are squeezing the door / cover of the book to the left. There is no 3D feel to it, I'm afraid. Any suggestions of how to make this better would be welcome:
-(void)openDoorTo:(UIViewController *)aController duration:(float)aDuration {
[aController viewWillAppear:YES];
[activeController viewWillDisappear:YES];
[self.view insertSubview:aController.view belowSubview:activeController.view]; // so that it is below activeController
[aController viewDidAppear:YES];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:aDuration];
aController.view.transform = CGAffineTransformMakeTranslation(0,0);
CATransform3D _3Dt = CATransform3DIdentity;
_3Dt = CATransform3DTranslate(_3Dt, -320, 0, 0);
_3Dt = CATransform3DRotate(_3Dt, M_PI * 1.5, 0.0, 1, 0.0);
_3Dt = CATransform3DTranslate(_3Dt, 320, 0, 0);
activeController.view.layer.transform = _3Dt;
[UIView commitAnimations];
[self performSelector:#selector(animationDone:) withObject:aController afterDelay:aDuration];
}
I think the main problem is that I don't really know how to handle CATransform3DTranslate and CATransform3DRotate.
Here are some posts on this, but I can't really see how to apply them to a 3D Door opener:
3D Door - but Axis in the middle
3D Unfolding
You have to apply a little trick to get 3D to work:
CATransform3D _3Dt = CATransform3DIdentity;
_3Dt.m34 = 1.0 / -1000;
This creates perspective transform. 1000 represents the distance from the viewer to the objects (you can experiment with this value to get a smooth look).
Related
I was to able to move or animate my UIView by this code down here:
- (void) makeAnim1{
//downward animation
[UIView animateWithDuration:1.5
delay:0.15
options: UIViewAnimationCurveLinear
animations:^{
carousel.frame = CGRectOffset(carousel.frame, 0, 650);
}
completion:^(BOOL finished){ //task after an animation ends
[self performSelector:#selector(makeAnim1_1) withObject:nil afterDelay:2.0];
NSLog(#"Done!");
}];
}
- (void) makeAnim1_1{
//upward animation
[UIView animateWithDuration:1.5
delay:0.1
options: UIViewAnimationCurveLinear
animations:^{
carousel.frame = CGRectOffset(carousel.frame, 0, -650);
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
But it only move the UIView up and down. How can I make it spin like a Slot machine but only contains one image or view. Like rotate in z axis. But make it look like it contains more than one image.
Thanks for the help.
Instead of changing the frame inside the animation block you change the transform. The transform can be used to scale, rotate and translate (move) the view. You can only rotate around the z-axis but that is what you asked for. The transform property on the view takes a CGAffineTransform, like this:
// rotate pi/2 degrees clockwise
carousel.transform = CGAffineTransformMakeRotation(M_PI_2);
If you need to do more advanced transforms like rotating around another axis then you would need to use a little bit of Core Animation and to set the transform property of the views layer (which takes a CATransform3D instead of a CGAffineTransform).
As with all Core Animation code you need to import QuartzCore.framework and include QuartzCore/QuartzCore.h in your code.
The above animations you are doing is UIView animations which are only meant to animate views but the animation you are asking for requires more advanced animations of the views layer. I suggest that you look at the documentation for CABasicAnimation and also take a look at the Core Animation Programming Guide for iOS to learn more.
You can animate the x rotation of a views layer like this:
CABasicAnimation *slotAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.x"];
[slotAnimation setToValue:[NSNumber numberWithFloat:M_PI_2]];
// animation customizations like duration and timing
// that you can read about in the documentation
[[carousel layer] addAnimation:slotAnimation forKey:#"mySlotAnimation"];
The above code will indeed rotate the view around the x axis but will look very silly without perspective (search SO for "perspective Core Animation", it has been asked about before). There is probably a lot of tweaking to get the correct look but this should be enough to get you started.
Well here is my problem:
I have two images : flakeImage and ViewToRotate. What I want is that if flakeImage touches ViewToRotate, ViewToRotate.alpha=0.5; but when FlakeImage appears on the screen ViewToRotate.alpha=0.5; without touching it. I think it's a problem with my view beacause I have :
UIImageView* flakeView = [[UIImageView alloc] initWithImage:flakeImage];
here is the code :
UIImageView* flakeView = [[UIImageView alloc] initWithImage:flakeImage];
// use the random() function to randomize up our flake attributes
int startY = round(random() % 320);
// set the flake start position
flakeView.center = CGPointMake(490, startY);
flakeView.alpha = 1;
// put the flake in our main view
[self.view addSubview:flakeView];
[UIView beginAnimations:nil context:flakeView];
// set up how fast the flake will fall
[UIView setAnimationDuration:7 ];
// set the postion where flake will move to
flakeView.center = viewToRotate.center;
// set a stop callback so we can cleanup the flake when it reaches the
// end of its animation
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
How can I solve this please ?
if someone could help me it would be very cool.
I have a bit of sophomoric experience with this, having written http://itunes.apple.com/us/app/balls/id372269039?mt=8. If you check that app out, you will see a bit of the same problem. This topic is a pretty deep rabbit hole. WHen I started that app, I didn't even know how to write a decent game loop. You will need that first because you need to do precise time-step calculations. AFA the collisions, you update your model and view separately, so if you update the model and objects overlap, you need to back them up until they don't collide and then update your view with the result. If you plan to have a lot of colliding objects, you may hit a wall using UIViews. To complicate things more, if your objects are round, CGRectIntersectsRect won't exactly work. That complicates the math a bit, but it's not too bad. With my app, I found it quite difficult to get the physics to look realistic. THe problem became that ball A and B overlap, so you back them up, then they now intersect other balls, etc, etc. This link is a good starting point, but there are quite a few examples of code out there that "almost" work.
CGRect has a intersection function. The frames of UIViews are CGRects.
if (CGRectIntersectsRect(view1.frame, view2.frame) == 1)
NSLog(#"The views intersect");
else
NSLog(#"The views do not intersect");
The problem I foresee is that if the rects have lots of whitespace, they will appear to intersect before they actually touch
Secondly, you should switch up to block animations. It's strongly encouraged
UIImageView* flakeView = [[[UIImageView alloc] initWithImage:flakeImage] autorelease];
// use the random() function to randomize up our flake attributes
int startY = round(random() % 320);
// set the flake start position
flakeView.center = CGPointMake(490, startY);
flakeView.alpha = 1;
// put the flake in our main view
[self.view addSubview:flakeView];
[UIView animateWithDuration:.7
animations:^ {
// set the postion where flake will move to
flakeView.center = viewToRotate.center;
};
Did this all from memory, no idea if there are errors.
Circular Collision:
a^2 + b^2 < c^2 means they collide
if(pow(view2.frame.origin.x - view1.frame.origin.x, 2) +
pow(view2.frame.origin.y - view1.frame.origin.y, 2) <
pow(view1.frame.size.width/2, 2))
{
//collision
}
else
{
//no collision
}
Again, all from memory, check for errors on your own
Right now I'm populating a UIScrollView with a series of views. The views need to be warped to make the UIScrollView appear like a carousel. In other words when the user scrolls it needs to be like a circle. I've never done anything quite like this before, but I'm assuming CoreAnimation is out of the question and OpenGL needs to be used. If this is possible with CoreAnimation or Quartz then I really just need a sample on how to warp the views and I can figure the rest out myself but I'm not familiar with OpenGL.
If you want to warp the views, you'll either need OpenGL or you could use Core Animation's CAShapLayer which allows you to specify a bezier path which can have this curve in it. But keep in mind that this curving you're seeing is likely just an optical illusion (though in your image above it looks like an actual curve). If you get enough rectangles with the correct y axis rotation in a row, I think you can come up with the effect you're looking for with straight Core Animation. I'm pretty sure that's how things are implemented in the Core Animation demos Apple provided a couple years ago. Here's a screenshot from the video from that presentation:
I messed around with the transform of a view's layer a little bit and came up with this:
- (IBAction)sliderDidChange:(id)sender
{
CGFloat value = [(UISlider*)sender value];
CGFloat xOff = value - 0.5;
CATransform3D trans = CATransform3DIdentity;
trans.m34 = 1.0f / -1000.0f;
trans = CATransform3DRotate(trans, degreesToRadians(xOff * -25.0f), 0.0f, 1.0f, 0.0f);
trans = CATransform3DTranslate(trans, 0.0f, 0.0f, 900.0f * fabs(xOff));
[[frameView layer] setTransform:trans];
CGPoint center= [frameView center];
[frameView setCenter:CGPointMake(1024.0 * value, center.y)];
}
I threw together a demo project that shows how the rotation works in response to a slider. It doesn't use a scroll view so you would have to adapt it, but I think you can track the current scroll offset and apply the transform accordingly. Not sure if it will help but there it is.
In my experience, it is a bit tricky to get the values right. The way to give a view perspective is by manipulating it's layer transform. I have used the following method to achieve the transfor for a similar effect:
-(CATransform3D)makeTransformForAngle:(CGFloat)angle from:(CATransform3D)start{
CATransform3D transform = start;
// the following two lines are the key to achieve the perspective effect
CATransform3D persp = CATransform3DIdentity;
persp.m34 = 1.0 / -1000;
transform = CATransform3DConcat(transform, persp);
transform = CATransform3DRotate(transform,angle, 0.0, 1.0, 0.0);
return transform;
}
This was done to create a "flip page" animation, so you may need to adapt. To use it, do the following:
flip_page.layer.transform = [self makeTransformForAngle:angle from:CATransform3DIdentity];
where flip_page is a UIView. Cheers!
CoreAnimation is a pretty easy thing, but:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:30];
MyImageView.frame = CGRectOffset(MyImageView.frame, 100, 0);
[UIView commitAnimations];
I want to move the ImageView by 100 Pixel veeeery slowly. Therefore all positioning values are double I expect the Layoutsystem to position the items with subpixel accuracy.
Butt when I watch this animation i see the ImageView "jumping" pixelwise instead of a smooth traveling.
Any ideas to come to a real subpixelpositioning?
I also tried to set the position with a timer and recalculate the frame-values, but same effect.
Update:
In an other part of my App I use the Accelerometer to update the position of a ImageView, and do basicly calculate the position ad size of the graphic an then do:
MyImageView.frame = newCGRect;
I get around 60 Updates/s from the Accelerometer and added the LowPass-Filter from the Accelerometer example from Apple.
Here the positioning is perfect?!?!
Why does this do not happen with CoreAnimation?
Thanks for any help.
Try using CGAffineTransformTranslate(MyImageView.transform, 100, 0) instead of CGRectOffset.
Reference here:
http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CGAffineTransform/Reference/reference.html
If you use CABasicAnimation in QuartzCore framework, you can smoothen your animation using "CAMediaTimingFunction". Built-in alternatives worked for me but as far as I know you can define your own timing functions as well.
CABasicAnimation *starShineAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
starShineAnimation.removedOnCompletion = NO;
starShineAnimation.fillMode = kCAFillModeForwards;
starShineAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
....
Sorry if this is obvious or covered elsewhere, but i've been googling all day and haven't found a solution that actually worked.
My problem is as follows: I am currently drawing an image in a full screen UIView, for examples sake we'll say the image is in the bottom right corner of the UIView. I'd like to do a rotation transform(CGAffineTransformMakeRotation) at the center of that image, however, by default the rotation command rotates around the center of the UIView it self. As a result, my image moves around the screen when i rotate instead of it staying in place and rotating around its own center.
From what i've gathered, i need to translate my context so that the origin(center of the UIView) is at the center of my image, Rotate, and then restore the context by translating back to the original spot.
The following is the closest thing i've gotten to work, but the problem is that while the image is rotating, it moves downward while it's rotating. I think this is caused by animation tweening the 1st step translate and 3rd step translate instead of just realizing that the beginning and end point on the translates would be the same...
// Before this i'd make a call to a function that draws a path to a passed in context
CGAffineTransform inverseTranslation = CGAffineTransformMakeTranslation( transX, transY );
CGAffineTransform translation = CGAffineTransformMakeTranslation( -transX, -transY );
CGAffineTransform rot = CGAffineTransformMakeRotation( 3.14 );
CGAffineTransform final = CGAffineTransformConcat( CGAffineTransformConcat( inverseTranslation, rot ), translation );
// Then i apply the transformation animation like normal using self.transform = final etc etc
I've also tried stuff like CGContextTranslateCTM and CGContextSaveGState/UIGraphicsPushContext, but these seem to have little effect.
I've been fighting with this for days and my current solution seems close, but i have no clue how to get rid of that translating tweening. Does anyone have a solution for this or a better way to go about this?
[update]
For the time being i'm drawing my image centered at the UIview's center and then setting the UIView.center property to the origin i'd like to rotate and then doing the rotate command. Seems like a bit of a hack, but until i can get the real translates working it's my only choice.
Thanks!
duncanwilcox' answer is the right one, but he left out the part where you change the anchor of the view's layer to the point you want to rotate around.
CGSize sz = theview.bounds.size;
// Anchorpoint coords are between 0.0 and 1.0
theview.layer.anchorPoint = CGPointMake(rotPoint.x/sz.width, rotPoint.y/sz.height);
[UIView beginAnimations:#"rotate" context:nil];
theview.transform = CGAffineTransformMakeRotation( 45. / 180. * M_PI );
[UIView commitAnimations];
This is documented here: http://developer.apple.com/documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/Layers.html
This is also an option: a simple change of basis ;-)
CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformTranslate(transform,-x,-y);
where (x,y) is the rotation center you like
Rotation happens around the anchorPoint of the view's layer. The default for the anchorPoint is the center (0.5, 0.5), so the rotation alone without the translations should suffice.
Did a quick test and this works for me:
[UIView beginAnimations:#"rotate" context:nil];
self.transform = CGAffineTransformMakeRotation( 45. / 180. * 3.14 );
[UIView commitAnimations];
If you don't want an animation to occur, but just set the new rotation, you can use this:
CGFloat newRotation = 3.14f
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.0]; // no tweening
CGAffineTransform transform = CGAffineTransformMakeRotation(newRotation);
self.transform = transform;
[UIView commitAnimations];
The rotation should indeed take place around the center of the UIView. Setting the animationDuration to zero garantees no tweening should happen.
Be sure though you don't do this 60 times a second. It's very tempting to create game like animations with these tools. These kind of animations aren't exactly meant for a frame to frame based, "lots of sprites flying everywhere" game.
For that - and I've been down that road - the only way to go, is OpenGL.