Animation inside a UIScrollView - iphone

I want to fade-out a view as it is scrolling inside a parent UIScrollview. When the fade-out animation begins, the scroll view stops scrolling. It jumps to the correct position when the fade is complete.
My fade-out is achieved with animateWithDuration and block objects, triggered upon a page-change I detect in scrollViewWillBeginDragging.
Does anyone know how to make them both happen simultaneously? Just to be clear, I am not 'animating' the UIScrollView scrolling - rather it is happening via user interaction of swiping.
EDIT:
Here is the code I'm using to fade the UIView. This code is in a UIViewController derived class, which is the delegate for a UIScrollView. When the user starts dragging his finger, I want to fade out the subView. But when the user starts draggin a finger, the subview fades and the scrolling stops. After the subView has completely faded out, the the scroll view will then snap to the location where the user's finger is.
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView
{
[UIView animateWithDuration:0.5
animations:^
{
self.subView.alpha = 0.0f;
}
completion:^(BOOL finished) { }];
}

A little late, but if you want to keep using blocks, you can use:
animateWithDuration:delay:options:animation:complete:
add "UIViewAnimationOptionAllowUserInteraction" to options to allow interaction while scrolling.
I'm sure that you will still have the lag problem. Here's the best way I can explain it. Please forgive me in advance since I'm probably using the wrong terms. All animations must run on the main thread. When you call an animation, iOS first *P*rocesses then it *R*enders before it generates *F*rames. It looks like this.
PPPPRRRRFFFFFFFFFFFFFFFFFF
But since ScrollViews don't know how long your animation is going to be or when it will end, it has to perform the animation like this.
PRFPRFPRFPRFPRFPRFPRFPRF
My theory is that the lag you are experiencing has to do with these two calls colliding on the main thread at the same time. I'm not sure how you would solve this problem other than with a faster chip. I've that you could push one animation to the CPU and one to the GPU, but I'm not that advanced at programming yet.

very interesting ... I've checked this out, and yes, i have the same effect ... Well, it seems that the animateWithDuration somehow blocks the main thread ... which is not logical, and the documentation doesn't say anything about it either ..
However there is an easy workaround, something similar to this: (i've set the animation duration to 3 so i can see that it's working while i'm moving my scroll view :) ...)
[UIView beginAnimations:#"FadeAnimations" context:nil];
[UIView setAnimationDuration:3];
self.subview.alpha = 0.0f;
[UIView commitAnimations];

I would suggest, since the opacity is based on the user's finger's movements in the UIScrollView, using the delegate method scrollViewDidScroll:. The scrollView passed as a parameter can be used to check the contentOffset which is simply a CGPoint indicating how far into the content view of the UIScrollView the user has scrolled. Something like this can be used to relate the scroll position to the opacity of a given view in a paginated UIScrollView:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// The case where I used this, the x-coordinate was relevant. You may be concerned with the y-coordinate--I'm not sure
CGFloat percent = ((int)(scrollView.contentOffset.x) % (int)(scrollView.frame.size.width)) / scrollView.frame.size.width;
if (percent > 0.0 && percent < 1.0) { // Of course, you can specify your own range of alpha values
relevantView.alpha = percent; // You could also create a mathematical function that maps contentOffset to opacity in a different way than this
}
}

According to information that is still not supposed to be widely released, all iOS 4.x versions completely block user interaction while the animation is in progress.
Isn't it interesting, though, that you're UITouches are obviously still registered during the animation? Hmm... maybe that HINTS that something NEW is coming in a yet-to-be-released version!
I.e., If you can, read the iOS 5 Beta documentation on UIView class methods.

Related

CoreAnimation gets jerking

I've created an UIView and some layers. I've organized those layers into superview-subview hierarchy with the root in UIView's layer, added some gesture recognizers and I am trying to manipulate layers' geometry on events from gesture recognizers (setting bounds and position for sure). I use implicit animations and CATransactions. No explicit animations are used.
The actual result is good enough if I create CATransaction with duration = 0. But if I set the duration to 0.2 I get some strange results: as event come not rapidly and previous transaction have time to be completed before the next starts everything is OK (for example on tapping), but if there is not enough time (for example on pinch or pan) the layer being resized starts jerking. Visually it looks like animation rolls back to the initial point and starts again to the new value.
I do not see any reason for this. I've tried to perform the layout in -[UIView layoutSubviews] and invoke setNeedsDisplay in gesture recognizers handlers. I've also tried to separate this logic into separate method but it does not help.
I repeat once more that I use only implicit animations. And what I want to know is why does it happen (but not how to work around).
Any clues?
I think, it's because you start new animation when previous not finished.
Maybe, you need to stop it like
[yourView.layer removeAnimationForKey:#"yourAnimationKey"];
and you need to implement animation delegate method
-(void)stopAnimation:(id)sender
{
yourView.frame = [[yourView.layer presentationLayer] frame]; // that set frame of your view to it's animation position
}

Is it a problem when an iAd may be obscured?

I added the ADBannerView to a view and when I load the app I get the following message:
ADBannerView: WARNING A banner view (0x7a023c0) has an ad but may be obscured. This message is only printed once per banner view.
As far as I can see the entire banner is visible on the screen. Is this really a problem? Or is it only a warning that I can ignore?
As Stephen Darlington says, it's a good idea to figure out what the issue is. An easy way to double-check this in code (from a view controller) would be:
// bring your bannerView to the front
[self.view bringSubviewToFront:bannerView];
// and make sure it's positioned onscreen.
bannerView.frame = CGRectMake(0.0, 0.0, bannerView.frame.size.width, bannerView.frame.size.height);
Assuming you had an iVar / IBOutlet to your AdBannerView called bannerView, this would take care of any interface builder positioning issues, and make sure bannerView wasn't covered by anything.
From my experience, nothing bad happens if the ad is offscreen, however, the iAd will not load new ads until it knows it is fully onscreen. So, as you start up your app,
Your AdBannerView will attempt to load an advertisement, whether it is onscreen or not.
Depending on whether or not it is successful, your AdBannerViewDelegate will receive either
a) bannerViewDidLoadAd: (proceed to step 3) or
b) bannerView: didFailToReceiveAdWithError: (the AdBannerView will try again on its own)
At that point, the ball is in your court as to what to do with said bannerView, if in fact it did load an ad. An easy way to check for this in code is yourBannerView.bannerLoaded, which will return YES if it has an ad, or NO if it doesn't. And so...
How you handle the AdBannerView after it successfully loads its initial ad determines how it will behave in the future. You do not have to place it onscreen immediately -- choose a time that makes sense within your application. However, a banner view that has successfully loaded an ad will NOT try to load another one until it is onscreen. (Makes sense, right?) The tricky part is....
4b) you also won't get any new delegate messages from that bannerView, so if you're not moving the bannerView onscreen immediately upon getting the bannerViewDidLoadAd delegate message, you'll have to implement some kind of control structure on your own to handle when, if at all, you DO move it onscreen, at which point it will begin asking the ad server for more ads, and you'll get more delegate messages, and the cycle begins anew.
So, to sum up: It's only a problem if your iAd is obscured if you'd like to serve more iAds and get paid. However, eCPM has been very, very low lately, so maybe that's not such an issue after all ;)
To add to this discussion, I received this message when I modified the center property to move the ad just outside the screen. I use UIView animations to slide the ad onto the screen.
After some experimenting I figured out how to do this without causing the message to appear. The trick was to hide to set the adBannerView.hidden property to YES while waiting for the ad to load. Once it was loaded, I just had to make sure to set the hidden property to NO only after committing the animation:
-(void) fadeAdIn:(UIView*)view
{
float offsetY = view.frame.size.height * (bannerOnBottom ? 1 : -1);
CGPoint pos = [self getBannerPosition];
view.center = CGPointMake(pos.x, pos.y + offsetY);
[UIView beginAnimations:#"AdIn" context:nil];
[UIView setAnimationDuration:1.0];
view.center = pos;
[UIView commitAnimations];
// must unhide AFTER animation has been committed to prevent "ad obstructed"
view.hidden = NO;
}
Like compiler warnings, I think this is something that you should probably try to get to the bottom of even if it's not immediately causing problems. If I were Apple, I'd send my ads to apps that actually show them (I'm not saying they do do this), so there could be a financial aspect too.
A couple of problems that I've seen:
The iAd frame is slightly off, maybe off the screen by just a pixel or two
You've accidentally created two iAds and one is on-screen and the other is hidden behind the first
I got the same problem but the reason is I have OpenFeint notification bars on top of that, e.g. high scores, achievement unlocked, etc. It slides in and then slides out, does not stay for long, so I don't think it is a problem.
I you put ADS on the top, then user would not see OpenFeint notifications, that will be another problem, I do not know if this happens if you have ADS and OpenFeint on different locations of screen, I did not try it as my app's bottom screen is full of buttons, so only top of screen is available.
Another option is to listen for status bar resize events and move the iAd when that happens so that it isn't respositioned offscreen (resulting in that warning and no served Ads).
In your app delegate, tap into this function:
(void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame
// Check newStatusBarFrame.size.height and animate your iAd frame up or down accordingly.
I am obtaining this message when I add a ADBannerView to a UIScrollView. In this case the ad may be obscured.
The answer from LearnCocos2D was the solution for me. Not sure if this is a specific issue with Cocos2D (which I am using). My problem was I was using the "new" style animations using blocks which Apple recommends using, animateWithDuration:delay:options:animations:completion: When I use these, I get the obscured warning. I guess the problem is the view is partially obscured while it's animating in, hence the warning, but you can't make it completely visible before it's done animating, obviously, unless you just want to pop it on screen, which is ugly.
That's OK, because switching back to the "old" style animation using beginAnimations: and commitAnimations: did eliminate the warnings for me. I'm curious if this warning means you are actually missing out on ad revenue or if it's just annoying but not actually a problem.
I had code like this:
if (animate)
{
[UIView animateWithDuration:0.5 animations:^{
self.adBannerView.frame = adBannerFrame;
self.otherViewFrame.frame = otherViewFrame;
}
];
}
else
{
self.adBannerView.frame = adBannerFrame;
self.otherViewFrame.frame = otherViewFrame;
}
and after some experimenting, I found that the order of the initializations should be reversed in both if and else legs.
self.otherViewFrame.frame = otherViewFrame;
self.adBannerView.frame = adBannerFrame;
So the idea was not to let another view cover the AdBannerView, even for a few microseconds.

layoutSubviews during an animation?

I have a UIView with a bunch of subviews, all positioned using layoutSubviews. When the view is resized, the relative positions all change. I'd like these re-calculations to happen during an animated resize (using +[UIView beginAnimations:] calls). This doesn't seem to be happening. Any ideas?
Assumption: You want to have multiple animation steps (i.e. position doesn't change linearly with frame size).
This isn't possible with a single "standard" UIView animations. Why? The frame/bounds is only set once.
Core Animation has three "layer trees":
The model tree is where your app thinks things are.
The presentation tree is approximately what's being displayed on screen.
The render tree is approximately what Core Animation is compositing.
UIView is a (somewhat thin) wrapper around the model layer. During a UIView animation, Core Animation updates the presentation/render tree — the model tree represents the endpoint of animations. The upshot is that your code can (for the most part) treat animations as instantaneous — moving a view from A to B instantly moves it to B; the change just happens to be animated to the user.
There are more complicated things you can do with CALayer/CAAnimation directly, but I haven't investigated this much.
You could chain multiple animations together using -[UIView setAnimationDidStopSelector:]. (You could also try using multiple animations together with setAnimationDelay:, but I'm not sure what happens with multiple animations on the same property; you might have luck with setAnimationBeginsFromCurrentState:.)
If you want really fine-grained control, CADisplayLink (OS 3.1+) is a timer that fires after each screen refresh. A fallback option (for 3.0 support) is to use an NSTimer at 30/60 Hz or so.
I know this is an old question, but this code works for me very well (suited for your example of changing frame).
-(void)layoutSubviews{
[super layoutSubviews];
// layout your subviews here, or whatever
}
-(void)someMethod{
double duration=...;
[UIView animateWithDuration:duration animations:^{
self.frame = ...;
[self layoutIfNeeded];
}];
}
Of course you can call this method from another object. The "trick" is to call layoutIfNeeded (or layoutSubviews directly - same thing, if You change the frame the setNeedsLayout is called).
As tc. nicely explained the "layer trees", You just force the presentation layer to display the final stage of model layer with animation.
The advantage of this method is in possibility to control when the frame/bounds change is animated and when it's instant.
Hope this helps someone:).
Completing #GrizzlyNetch's anwer, you can set the UIViewAnimationOptionLayoutSubviews animation option, so you don't need to call layoutIfNeeded:
-(void)someMethod{
double duration = ...;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{
self.frame = ...;
} completion:nil];
}
Posting for completeness. Thanks to tc. for explaining that what I want to do, exactly, is not supported by Core Animation.
I eventually came up with a reasonable solution. Rather then layout my subviews in -layoutSubviews, I do so in -setBounds:. Then, when I wrap a -setBounds: call in a UIView +beginAnimations: block, those positioning calls are also animated, and the end result is everything properly animating to where it should god.

How do I add animation to an iPhone app?

So I came from a Flash background where I can animate in timeline. I've completed the Beginning iPhone Development book and just realized that I still don't know how to get an animation in. I'm guessing I need to import png sequences?
Can anyone point me to an appropriate place to learn more about this topic? I want to make a game and my game objects need to animate.
Thanks in advance!!
The simplest type of animation, moving things around and fading in and out, can be done with a few static methods of UIVIew. You can affect the center, bounds, transform matrix and alpha level of one or more views.
[UIView beginAnimations:nil context:nil];
[fadingOutView setAlpha:0.0];
[slidingView setCenter:CGPointZero];
[shrinkingView setFrame:CGRectZero];
[fadingInView setAlpha:1.0];
[spinningView setTransform:CGAffineTransformMakeRotation( M_PI )];
[UIView commitAnimations];
Animations start with the current state of the view and interpolate to the state assigned between begin and commit animation. So if fadingInView already had an alpha of 1.0 (the default) there would be no change.
If you are unfamiliar with static methods [UIView method]; means call method on the class not an instance.
Using other UIView static methods you can control several details of the animation. Every UIView has a CALayer that also has a few properties that can be animated, the most interesting of which is the 3D transform property.
If the basic animation is not sufficient for you needs, you can either look into CAAnimation and related classes, or look to a third party animation library.
I think the best place to start learning is in your code, since you are just transitioning from Flash. Look at the very bottom of UIView.h to see the animation methods. Make a few views and move them around.
Take a look at cocos2d for iPhone. It has a good framework for handling sprites, animation (frame based and motion) as well as a lot of other stuff.
http://www.cocos2d-iphone.org
You can of course do all off this on your own with core graphics and core animation, but an API like cocos2d takes care of a lot of the nitty gritty details for you.
The Beginning iPhone Development book does not talk much about animation. You may read more about animation from Apple documentation, and play with some samples from Apple, such as the Touches.

Can I increase the animation speed of presentModalViewController?

I'm writing a drawing application that shows a tools view controller when the user clicks an item in a toolbar. However, several of my beta testers have reported that the tools palate opens too slowly. I'm using the standard presentModalViewController:animated: call to display the tools, and I've tried wrapping it in a code block like this to speed it up:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration: 0.1];
[self presentModalViewController:settings animated:YES];
[UIView commitAnimations];
Unfortunately, that doesn't work. If you say animated:NO it works better, but the underlying drawing canvas view is removed immediately (since the controller thinks it is no longer visible), and so the animation occurs over a white background.
Has anyone done this before that would be willing to offer some advice? I'd appreciate it!
Edited: added another option with controller containment for iOS 5 and later.
Another solution is to set the layer's time space.
This is done through the speed property of CALayer. To slow the animation down, one could use:
MytransparentVCViewController *vc = [[MytransparentVCViewController alloc] initWithNibName:#"MytransparentVCViewController" bundle:nil];
// Makes all animations 10 times slower
// To speed it up, set it to multiples of 1: 2 is 2 times faster, 3 is 3 times faster etc
vc.view.layer.speed = 0.1;
[self presentModalViewController:vc animated:YES];
Note that the proposed solution in the linked post will not work if your objective is to change the animation speed of the modal view controller you are about to present (for example if you use UIModalTransitionStyleCoverVertical).
The layer's speed is not an absolute value but a function of that layer's parent time space (unless the layer is in the root of the layer hierarchy of course). For example, when you set a layer's speed to 2, its animations will run twice as fast in comparison to that layer parent's animations.
Yet another option is to use view controller containment. (iOS 5 and later only)
http://developer.apple.com/library/ios/DOCUMENTATION/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW81.
You have full control over the animation with UIViewController's transitionFromViewController:toViewController:duration:options:animations:completion:.
A similar question is asked here.
You can also change the speed using this technique, but in my experimentation, it does so over a blank background, as you've suggested.