I am banging my head on the wall here due to this problem:
When I create a UIImageView this view has a certain orientation and size. Lets call this state "A".
This view responds to taps. It can be dragged around the screen.
At some point in the code I apply a CGAffineTransform to the view. Does not matter if the affine is a scale, a rotation, a translation or a combination of all. Does not matter also if the transform is absolute or relative. Not to mention the device can change its orientation and the view is autorotated to the correct orientation (that we can cay is a kind of rotation or transformation applied to the view).
The problem is: the moment I touch that object or try to animate its transparency or any other parameter, it "remembers" the state "A" and does all animations from that state, not from current state. If I simply touch the view, it returns instantly to state "A". The code is not doing it by itself. It is pretty annoying. How to I make a view assume its current state of transformations as the reset or initial state? In other words, how do I make a view forget its past transformations or states?
The only way I know is recreating the view, but this is a ridiculous way of doing this.
Is there any way to make this work as I described?
thanks
Afaik, all of the SDK animations automatically create a copy, perform the animation on the copy while hiding the original. In your code you'll have a getState line that starts this and creates the pointer to the animation object. To make it permanent at the end of your animation routine set the original objects view to the animation view.
Iirc it something like this, but I don't have my code samples in front of me:
myOriginalObject.view = myMnimationObject.view
Obviously do this before you release your animations but after you're done with the transforms.
Related
I have a UIView which has an X origin that makes it off screen to the right. Then, I do a keyframe animation with CATransform3D:
[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-view.width, 0, 0)]
The problem is, after the animation completes, the view's frame is visually in the correct place, but it still thinks it's off screen, so I can't interact with it. Logging its frame property also shows that it's offscreen, but visually, it's not.
The fill mode for the animation is kCAFillModeForwards, so the final value of the animation sticks.
What is the solution to this problem, that is, interacting with this view after the animation and notifying the view that it is indeed visible?
You can use CAAnimation delegate method -animationDidStop:finished: to fix it when the animation done:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
...
}
The better answer is to use UIView animation methods like animateWithDuration:animations:completion: (and variants on that method.)
Those methods let you do animations on the actual view properties, and when the animation is complete, the view is at it's actual destination location.
The center property is the easiest way to move views around. The frame property isn't valid if you've applied a transform, but the center property IS always valid for both reading and writing.
Core Animation only creates the illusion that your views move. The animation takes place on a display-only copy of the layer tree. You can query the properties of an animating layer by reading those same properties from the layer's presentationLayer. However, the view objects will not respond to the user interaction at their apparent location. The views still think they are at their original locations.
As the other poster said, you could use the animation completion delegate method to move the view to it's final location once the animation is over, but that's a lot of fussy work to do something that UIView animation does for you much more cleanly and simply.
Can you interact with the view even before you animate it?
is the view.userInteractionEnabled is set to YES??
I have an EAGLView which I am resizing in an animation, with the animation driven by an NSTimer that calls a draw function. As I resize the EAGLView, I need to adjust the projection for the view to maintain the aspect ratio of the contents. The draw function looks like this:
gameView.frame = newFrame;
[gameView setFramebuffer];
[self updateProjection];
/* Drawing to the EAGLView, gameView, here, then... */
[gameView presentFramebuffer];
Run like this, though, the contents of the EAGLView appear to "stair-step" down the animation. When I record it and look at it frame by frame, it's clear that the projection is adjusted, then the view is resized a very short time later (less than one animation frame).
I suspect that the reason is that the change in the frame of gameView is being deferred, and that in the meantime the updated framebuffer is making its way to the screen. I've tried using CATransactions to get the frame update to take effect immediately, but as I kind of expected for a UIView change, that did nothing. I suppose I could modify the viewport and leave the EAGLView full-frame, but I worry that that might just leave me with synchronization issues elsewhere (say, with updates to any overlaid CALayers).
Does this seem like a reasonable assessment of the problem? How can I prevent it -- that is, how can I best ensure that the framebuffer presentation coincides with the change in the actual frame of the EAGLView (and other CA elements)?
Thanks!
I encountered a similar issue with a custom EAGLView I wrote. The problem was that when changing orientation, the view got resized and the contents stretched, and only after the animation, the scene with the right proportions was displayed.
I think yours is the same problem, and I don't think you can force CoreAnimation to wait for your view to update before rendering an animation frame, because of the way it works (CA isn't aware of the drawing mechanism of the view or the layer on which operates).
But you can easily bypass the problem: open you xib file containing the view, switch to the attributes inspector on the right. Select your EAGLView (the same applies to GLKView), and under the section View there's the Mode attribute. If you're creating the view programmatically, set the contentModeproperty.
This value describes how the view contents are managed during an animation. Until the buffer is displayed, the old scene gets resized according to that mode; perhaps you can find a mode that fits the animation you need to achieve.
If you're worried about proportions, the center content mode may work, but the part of scene that hasn't been rendered yet will appear empty during the animation.
Unfortunately the redraw mode won't necessarely make your EAGLView refresh: the view is actually redrawn, but potentially with the old contents of the color buffer. It's a problem of getting the buffer filled at the right time.
You could try to resize and prerender a frame big enough to cover the whole animation before starting it; or you could try to render a frame to an UIImage, replace the view on the fly, animate it, the place the EAGLView back, but this may seriously affect performance.
The way I solved my issue was making simply the EAGLView big enough.
Regarding the blending of other CALayers, as far as I tried, I encountered no synchronization issue; the EAGLView updates when it can, and the other layers too. Just set the right properties to CAEAGLLayer if you're using alpha channel, and rememeber that blending the layers every time OpenGL updates your scene may be expensive in terms of performance.
I have a single UIView for drawing any one of a set of items. That is, my one UIView subclass can be told to draw a square, circle, or triangle for example.
I'd like to have a transition to flip that view so that the UIView draws a different shape at the half way point, so it looks like I'm transitioning to a different view, but really I'm just redrawing the single view at the half way point of the transition.
Is there a way to do this, without resorting to using two different UIViews and swapping them out in the animation block? It would be a bit clumsy for me to rejig what I have to use the swap mechanism.
Ideally all I need is some callback or notification that the animation is at the half way point, and then I can redraw the view with the new shape then.
Thanks for any help!
Even if you did know the midpoint of your flip animation, I don't believe redrawing the content of the view mid-animation would do anything. I think that the contents of the view's layer are cached at the beginning of the animation and don't change as the animation proceeds. Also, your view would end up inverted, left to right, at the end of the animation if you didn't have another view behind it.
If you wish to do this as a custom animation, you could break this into two halves using something like Mike Lee's Lemur Flip implementation that I describe in this answer. After the first half of the animation, you could redraw the view's content and complete the animation, ending up back where you started.
However, to me it seems more clumsy to not switch views in response to the transition. It certainly will take a lot more code to do.
you can try to do smth like this. For example, your animationDuration property is set to 2.0f. so, the half way point is 1 second, so you can use
[self performSelector:#selector(yourCallbackMethod) withObject:nil afterDelay:1.0f];
The only way I know of that you could do this is to re-create the animation using Core Animation and then monitor the animation in the presentationLayer using a timer. There is no KVO available in Core Animation Layers so you have to monitor it explicitly.
In other words, it's probably not worth it. I suggest you think of a different way to solve your problem.
I'm making an app where one of the objects needs to appear not to move or rotate during AutoRotation, but everything else needs to rotate and be repositioned. The way I did this is by manually rotating the object, and moving it into the same position, relative to the device, that it had prior to rotation. The problem is that it looks funny, appearing to rotate into the same position it had before.
If I suppress the animation effects during rotation, it would appear that the object never moved, and everything else just snapped into place, which is what I want. I haven't been able to find anything in the documentation that tells me how to do this though. How do I do this?
When it comes to UIViewController rotation there are two ways you can set up so you get a chance to rotate and move your own views.
In the one-step process, in your UIViewController-derived class provide an implementation of willAnimateRotationToInterfaceOrientation. It gets called and you can kick off your object rotation so while the system is rotating everything else your object is getting counter-rotated so it looks like it's staying put. You'll want to calculate how much to counter-rotate and in which direction based on current interface orientation vs. new orientation.
The other way is to get notified to do custom rotation in two steps. For example, in the first half you can shrink the object down, move it, and rotate it part way then in the second half finish the rotation as you scale back up to normal size. It's a pretty clever way to make the rotation animation look smoother to the eye.
For the two-step process, you need to define two methods. willAnimateFirstHalfOfRotationToInterfaceOrientation gets called for the first half of the rotation (i.e. up to 45 degrees for a 90 degree rotation and at 90 degrees for an upside down flip). Once past that point the second half is called via willAnimateSecondHalfOfRotationFromInterfaceOrientation.
If your object has a 1:1 aspect ratio (i.e square or round) and in the middle of the view then the one-step process will probably work fine. But if it's a non-square object and has to move position (for example if it's at position 40, 60 in portrait but moves to 20, 100 in landscape) and maybe even needs a bit of scaling to look better then you may want to try the two-step process and see if it looks smoother.
If your object is inside its own individual UIView then it's pretty easy to schedule the rotations through UIView animations. Just create a transform through CGAffineTransformMakeRotation, then inside a pair of UIView beginAnimations/commitAnimations blocks set the transform property of the view to this value. You can tweak the timing through setAnimationDuration.
EDIT: Based on the comments, I'm adding some code to show how you could attach the view to the top-level window instead of to the view controller. Your object would then reside in this view instead of the one managed by controller (which is getting rotated). You still need to over-ride the UIViewController rotate methods, but instead of rotating an object under control of the view controller you would trigger a counter-rotation in the object on the top-level.
To add a view to the top-level window:
YourAppDelegate* windowDelegate = ((YourAppDelegate*) [UIApplication sharedApplication].delegate);
[windowDelegate.window addSubview:yourView];
Keep a reference to yourView somewhere you can get to, then in the UIViewController's willAnimateRotationToInterfaceOrientation counter-rotate yourView, i.e. calculate how much to rotate the view in reverse to where you're going--if the phone is turning 90-degrees clockwise, you'll want to rotate the view back 90 degrees counter-clockwise, etc. Then use UIView animations on yourView.
If you don't want rotation animations, overload -shouldAutorotateToInterfaceOrientation: in your UIViewController and return NO. You can then observe UIDeviceOrientationDidChangeNotification to receive manual orientation change notifications.
This may work:
Listen for willRotateToInterfaceOrientation
[UIView setAnimationsEnabled:NO];
Listen for UIDeviceOrientationDidChangeNotification
[UIView setAnimationsEnabled:YES];
Untested, but I know that does work for some cases.
I have found the UIScrollView's zooming mechanism to be clunk and essentially unusable. So instead, I'm rolling my own. I have a UIView that resizes itself with the pinch-zoom, and that's working fine. When the zoom is complete, the view needs to reset its transform and redraw the images.
The zoom works essentially in the same way the UIScrollView does. It sets the transform property of the UIView until complete. Then, when the zoom finishes, I want to reset the transform to CGAffineTransformIdentity, resize the frame to be the size it was before, and tell the view to redraw itself at the new size.
It all works pretty well, except when I change the transform to identity then redraw the image, there is a slight flicker before the image completely redraws. This is due to the fact that I'm using a subclass of CATiledLayer, since the view can be of arbitrary size.
I've overridden the fadeDuration to be zero, but there is still a flicker while the transform is reset before the redraw is finished. Is there any simple way to overcome this without creating another view to draw with then replacing it?
Problem solved using a lower-level approach to layers.