I'm crossfading between two views using UIView animation. I've noticed the following surprising fact:
If I have (say) two identical views in an identical place, and I animate a cross fade between them (e.g. animate the alpha from 0.0 to 1.0 on one while going from 1.0 to 0.0 on the other, in the same animation), during the animation, the visible result arcs slightly below opaque during the animation-- this is a noticeable artifact and can be verified by putting some other view behind the crossfaded views (it becomes visible briefly during the animation before being obscured again).
I would expect (using any animation timing curve) that perfectly paired 0->1 and 1->0 alpha transitions would always add up to a net alpha of 1.0, and that in this test situation, I should never see any visible change in alpha, yet I do.
Any idea what's going on here? I could hack around this for a "fix", but I'm mostly interested in what I'm missing conceptually in the blending.
Thanks!
Two stacked views with alphas adding up to 1.0 doesn't do what you think it does. They are multiplied, not added.
Let's take it a chunk at a time. Here's a background, shining through 100%:
bg
|======>
|======>
|======>
|======>
Now let's add another view on top, 50% opacity. That means it lets 50% of the background through
bg 50%
|===|===>
|===|
|===|===>
|===|
What if we have another 50% view on top?
bg 50% 50%
|===|===|===>
|===| |
|===|===|
|===| |
Another 50% of the stuff behind is passed through. This means that 50% × 50% = 25% of the background layer will still be showing through.
Now, what do you really want to do? You want the new view to appear smoothly, an increasing amount passing through the old view. So just stack the two views, and fade out the top one, but leave the bottom at 100% opacity the whole time. Otherwise you'll be showing some of the background through during the animation.
This is just a guess with no basis in actual fact, but since alpha is expressed as a CGFloat, I would advise against trying to assume anything about it adding up to 1.0 exactly, given the difficulty representing floating points with that type of precision. They likely add up to .99 or so, causing this artifact.
Related
Context
For the purpose of a MWE we will be using the following image of a stick figure:
with the goal of having a chain of these sprites move, hand-in-hand, across the screen:
It is worthwhile to note that the stick figure image itself is wider than the arm-span of this stick figure. The background is, however, transparent.
Depending on the application, one may make a class that either inherits from SKSpriteNode or encapsulates it, e.g. a class called Person, to store additional information, where there may be an array var people = [Person]().
Questions
1.) Suppose you had two instances of the aforementioned Person class with each sprite taking a stick figure image. How could one position them - programmatically - such that the sprites are touching ''hand in hand'' although the image has a transparent background? Of course one could spend some time fiddling about to get find a spacing parameter to ensure this is achieved, but that parameter would always have to be, via trial-and-error, re-calculated if the sprites were re-scaled.
2.) Given a chain of these sprites, hand in hand, how could one animate them to move across the screen at the same velocity? If one calculates the spacing parameter alluded to in 1.) then an SKAction could be given to each Person such that their end position is offset (but total distance traveled is the same), where the TimeInterval is maintained the same. Is there a way to tell all the sprites to move to the left until off the screen at a rate of $x$ pixels per second?
It looks like you've mostly answered your own questions already, but here are some additional ideas:
Make the spacing value proportional to the size of the sprite.
Yes, there is an SKAction that moves a sprite a given distance over a given period of time (effectively a velocity): let moveAction = SKAction.moveBy(x: 10, y: 0, duration: 2)
I need to change the size of my images according to their distance from the center of the screen. If an image is closer to the middle, it should be given a scale of 1, and the further it is from the center, the nearer it's scale is to zero by some function.
Since the user is panning the screen, I need a way to change the images (UIViews) scale, but since this is not a very classic animation where I know a how to define an animation sequence exactly - mostly because of timing issues (due to system performance, I don't know how long the animation will last), I am going to need to simply change the scale in one step (no timed animations).
This way every frame the functiion gets called when panning, all images should update easily.
Is there a way to do that ?
You could directly apply a CGAffineTransform to your UIImageView. i,e:
CGAffineTransform trans = CGAffineTransformMakeScale(1.0,1.0);
imageView.transform = trans;
Of course you can change your values, and or use other CGAffineTransform's, this should get you on your way though.
Hope it helps !
I'm repeatedly shrinking an image (and then render it to a new full sized image) by a small amount, and the result is that a stripe down the middle is not being shrunk. I'm assuming this has to do with the resize method cocos2d uses. If I increase the amount I scale down the image by the resize is too fast, and if I decrease the shrink size the bar down the middle gets even bigger! the following code is called 60 times a second. the picture below shows the result! So.. any suggestions on how to get rid of the bar?
[mySprite setScaleX:rtt.scaleX - .05];
I wasn't sure quite what you meant, but did you mean you're calling this line 60 times a second?
[mySprite setScaleX:rtt.scaleX - .05];
If so then your sprite's scale will become negative in a third of a second...
Every time you manipulate an image, you lose information.
A better approach would be to always resize from the original, and just change the resize amount each time, rather than continually resizing the result of the last resize operation.
I'm new to cocos2d engine, so hope this helps. If your shrinking an image, I would suggest using CCScaleBy. You can try something like this...
CCScaleBy *yourSprite = [CCScaleBy actionWithDuration: .01 scaleX: .95 scaleY: 1.0f];
This will scale your sprite down by 5% each time its called. Then you can have it replaced by the new image when it reaches what you would consider its smallest pixel point. The duration may need to be played with, but thought this would help.
I need to animate controls by moving them along the x axis from x to x-1000.
My container view is 200 pixels across and each control is 100 pixels wide.
There is a maximum of 4 controls (the controls are heavy and I need to re-use them)
So therefore, as I animate the controls from x to x-1000, I need to re-use them.
So as control 1 goes off to the left, it becomes invisible and needs to be re-positioned to the right hand side of the container view.
As I will be using an ease in function, the control needs to inherit the same speed and deceleration is it had before; so it literally just animates from right to left, once off screen, instantly re-positioned to the right of the container view, and carries on animating from right to left at the same deceleration rate.
Is there anyway to invoke a function for each frame of a CAPropertyAnimation? or something along those lines?
As far as I know, you can't get velocity information from core animation.
You will have to roll your own animation for this. Setup a timer, and give each view an xVelocity. Every time the timer fires, have it adjust the xVelocity of all objects (you could apply a sine curve to it to give it an ease/out effect). Then change the center point of all the objects.
I am trying to accomplish some complex effects during my UIView rotation, and I'm using the first half/second half method of rotation animations.
However, when my second half starts, I'd like to set some properties on my subviews (alpha, frame) etc, from which to begin animating in the second half. But setting any of these properties of course causes them to be animated. I'd like to say, set the alpha to 0.0, and THEN say, "ok, now animate it to 1.0 throughout the rest of the rotation."
Note that I can't set this property before the whole rotation; I want to affect its immediate value partway through.
Can this be done?
Thanks.
Implement this function in your UIControllerView :
- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration