Coming from ActionScript, I would set Sprites to visible=false in order to keep them from being calculated in things like layout, and to ensure they would not respond to events.
In iOS development, I am continuing with this--if a UIView is not needed, I may both animate its alpha to zero and then set hidden=true. I wanted to know if I was wasting my time, or if there is a benefit to this. In my current project, I'm doing so with UIImageViews which are not responding to events anyway.
Is setting hidden to true good practice, or or just additional overhead?
This is the best choice, because setting hidden to true removes view from the render loop. While setting alpha to 0 just makes view transparent.
If you don't need it any more, then you should properly remove it from memory. In that case you would just animate its alpha (to make it look good), then dealloc it.
if you autoreleased it, then all you have to do is remove it from the superview and its retain will hit 0 and be dealloced.
[UIView animateWithDuration:.5
animations: ^ {
[myView setAlpha:0];
}
completion: ^ (BOOL finished) {
[myView removeFromSuperview];
}];
Related
After looking for some feedback on how to clean up animations once they've finished I implemented the following;
- (void)onAnimationComplete:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
UIImageView *AnimationImageEnd = context;
[AnimationImageEnd removeFromSuperview];
[AnimationImageEnd release];
}
and to set them off (I've removed some of the frames and rotation calcs);
UIImageView * AnimationImageStart = [[UIImageView alloc] initWithImage: AnimationImage];
[self addSubview:AnimationImageStart];
[UIView beginAnimations:nil context:AnimationImageStart];
[UIView setAnimationDuration:iSpeed];
AnimationImageStart.transform = transform;
[UIView setAnimationDidStopSelector:#selector(onAnimationComplete:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
This seems to be working fine and cleaning up all used memory once the animation completes.
Since implementing this approach I have now got issues with re-opening the App once it goes into the background if an animation had started before hand. The App is now crashing with the following error;
*** -[UIImage _isDecompressing]: message sent to deallocated instance 0x21083ad0
I've had a tinker and it appears to be all down to the release used. If I remove the [AnimationImageEnd release]; the crashes stop (but it obviously starts some memory leaks off..).
Is there something simple I am misunderstanding about the Animations and the App entering / re-opening from the background?
My guess is that iOS is automatically cleaning up any animations in progress once it enters the background and automatically fires the release to each. If this is the case, should I just simply expect them to have been cleaned up and hold a flag within the applicationDidBecomeActive to bypass the release?
Edit 1
I've read that the finished flag on onAnimationComplete will let me know if the animation completed it's full animation or not. Is it enough to just trust that if it did not finish it has already been cleaned up (removed from superview and released)?
Edit 2
As per David's response below I have updated this to a block;
[UIView animateWithDuration:5.0f
delay:0.0f
options:0
animations:^{
imgStart.frame = CGRectMake( 0, 0, 10.0f, 10.0f);
}
completion:^(BOOL finished){
[imgStart removeFromSuperview];
//This still crashes the App as soon as the App returns from being in the background
[imgStart release];
}];
The same behaviour as detailed above is still present. As per Edit 1, is it enough to just assume that whenever the finished BOOL is NO, it has already been cleaned up (removed from superview + released)?
Any information is greatly appreciated.
Ralph
First, please don't use an initial capital letter for an object - reserve those for classes (it makes reading code much harder for most of us).
Generally it's bad practice to release objects in callbacks. Use a dispatch block to the main queue to release the object or a performSelectorOnMainthread:, and see if that helps.
EDIT: didn't mean to imply you needed a complete block. Reading the docs again shows no indication of retain change if the animation does not complete. Since you have a release in your code, you are not using ARC.
Non-ARC: so imgStart is alloc/inited, no autorelease, so its retain count is 1. You add it to the subview, its now two. You (or the system) removes it from the subview, its now 1. The final release makes it 0 (I'm ignoring the retain/release by the block itself). I just cannot see how the system could release it, as its not really anywhere it can be seen (its not an ivar, so viewDidUnload cannot release it). Right now this is a mystery.
Suppose you converted to ARC: imgStart is released at the start of the animation, two things retain it, the subview array and the block. When its removed from the subview array (by whoever), retain count goes down by 1, and when the block completes it will release it as well, causing the retain count to go to 0 so it will be released.
If nothing above rings a bell or helps, you can at least try and find when/where that object is getting dealloced. Create a UIImageView subclass in your .m file:
#implementation MyIV : UIIMageView
#end
#interface MyIV
- (void)dealloc
{
[super dealloc];
NSLog(#"MYIV DEALLOC!!!!");
}
#end
Put a breakpoint on the log message, and you can see exactly where its getting dealloc'd. I've found this so helpful in the past I created an ARC class to do this even for class clusters on github, called Tracker.
I have a UIView that I'm sliding in using a UIScrollView. Code looks like this:
[view setObject:object]; // updates a bunch of labels, images, etc
[scrollView addSubview:view];
[scrollView setContentOffset:CGPointMake(320,0) animated:YES];
Problem is, the scrollView doesn't seem to wait for the view to be completely loaded before animating its contentOffset, and so the animation is kinda jerky (almost non-existent on the first slide-in and on older devices). Oddly enough, switching the 3rd line to this fixes it:
[UIView animateWithDuration:0.3 animations:^{
[scrollView setContentOffset:CGPointMake(320,0) animated:NO];
}];
No lag whatsoever, perfectly smooth slide-in. However, this doesn't trigger any of the UIScrollViewDelegate calls, on which I also depend (but which aren't responsible for the lag).
Any idea of how I could tell the UIScrollView to wait for the view to be completely loaded before animating its contentOffset? Or maybe there's something else that I'm missing?
EDIT: before anyone else suggests it: yes, I did try:
[self performSelector:#selector(slideIn) withObject:nil afterDelay:1];
just to see if it would fix it. And yes it does fix the lag, but this is not an actual solution. performSelector:afterDelay: is never a solution, it's only a superficial fix. Plus, you're making the user wait extra seconds every time (since the actual delay may be much shorter than 1 second depending on the device model).
Performing selector after delay 0.01 works because the invocation is scheduled on the runloop vs. calling the method immediately. For that matter, afterDelay:0.0 may also work. Did not try in this particular case but works in may similar situations.
have you tried starting the scrolling in the "viewDidAppear" function of the viewcontroller... maybe this is called when the loading is finished.
otherwise try afterdelay with a very short time like 0.01 .. so that the call is scheduled next after the current work is done.
Try this, set contentSize after some delay (here delay is 2 second).
[view setObject:object];
[scrollView addSubview:view];
[self performSelector:#selector(contentSize) withObject:nil afterDelay:2];
}
-(void) contentSize {
[scrollView setContentOffset:CGPointMake(320,0) animated:YES];
}
I want to implement a simple animation based on UIView's center property. I have a simple view and I can drag it (UIView touchesMoved is overriden). The animation should fade slowly like the view is moved under it's own inertia for some time after the user releases it. But for now I want simply to move the view after touch ends. Here is the code I have in touchesEnded:
int i;
for (i=1;i<4;i++)
{
self.center = CGPointMake(10*i, 12*i);
[self setNeedsDisplay];
usleep(100000);
}
The problem is when I run this, the code is executed nicely but "UIView" changes to late. I changed usleep time and other parameters but the result is the same. It looks like all the "pending changes" in the view are performed only after the overriden touchesEnded is finished.
Is this the right way of implementing such user interface feature or should I look for some other approaches?
Thanks in advance.
If you know the final positions you want your view center to have you could do this with an animation:
[UIView animateWithDuration:20
delay:0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
youView.center = CGPointMake(newCenterX, newCenterY);
}
completion:^(BOOL finished){
}];
If that code is being run inside of touchesEnded you are blocking the main thread and that is why the animation happens all at once when its done. You will need run this loop in the background to update the view over time. When you update the UI remember to always call back to the main thread.
Some Threading Options:
performSelectorInBackground:withObject:
performSelectorOnMainThread:withObject:waitUntilDone:
Grand Central Dispatch
Threading
What is the built in method which can be used to hide and show toolsbars. specifying a rate or speed of the animation?
Look at this question, and do something like:
[UIView animateWithDuration:2.0
animations:^{
[self.navigationController setToolbarHidden:YES animated:YES];
}
completion:^(BOOL finished){
// whatever
}];
Toolbar's just a view—add an IBOutlet in your controller, then use UIView's (class methods) block animation methods, such as animateWithDuration:delay:options:animations:completion: or animateWithDuration:animations:. In the animation block, just move the view.frame.size.origin.y to a different location, or set its opacity to zero. the methods also allow you to specify the time period over which the animation will occur. Once complete (there's a delegate callback in the first method), you can then ask your main view to get taller by using the same methods to change the view.frame.size.origin.y of your main view.
I have a block of UIView animation code that looks like this:
[UIView beginAnimations:#"pushView" context:nil];
[UIView setAnimationDelay:0];
[UIView setAnimationDuration:.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationWillStartSelector:#selector(animationWillStart)];
view.frame = CGRectMake(0, 0, 320, 416);
[UIView commitAnimations];
The code basically mimics the animation of a ModalView presentation and is tied to a button on my interface. When the button is pressed, I get a long (.5 sec) delay (on iPod Touch...twice as fast on iPhone 3GS) before the animationWillStart: actually gets called. My app has lots going on besides this, but I've timed various points of my code and the delay definitely occurs at this block. In other words, a timestamp immediately before this code block and a timestamp when animationWillStart: gets called shows a .5 sec difference.
I'm not too experienced with Core Animation and I'm just trying to figure out what the cause of the delay is...Memory use is stable when the animation starts and CoreAnimation FPS seems to be fine in Instruments. The view that gets animated does have upwards of 20 total subviews, but if that were the issue wouldn't it cause choppiness after the animation starts, rather than before? Any ideas?
Try it with a single subview or with no subviews at all to make sure the delay is not caused by so many children.
Profile the code in Instruments to see where exactly the code lags. You might get down to some internal Core Animation function call that will hint you what’s going on.
Try the code without the “lot that’s going on” to make sure you’re not stepping on Core Animation’s toes with your other code.
Or, in short: experiment and measure, because conjectures seldom work when optimizing.
In your pasted block, you specify the selector animationWillStart (no colon), but later in your question, you refer to animationWillStart: (with colon). These selectors are not equivalent, so is it possible that your intended selector is never being called on account of this animation, and is being called 0.5 seconds later on account of some other animation?