I am using ARC in my app.
Now I faced a real problem. I need to animate two view controllers from left to right. So I just animate the the two views in button action like,
// thanks to stackoverflow for this code
test *control = [[test alloc] initWithNibName: #"test" bundle: nil];
control.view.frame = self.view.frame;
control.view.center = CGPointMake(self.view.center.x + CGRectGetWidth(self.view.frame), self.view.center.y);
[self.view.superview addSubview: control.view];
// Animate the push
[UIView beginAnimations: nil context: NULL];
[UIView setAnimationDelegate: self];
[UIView setAnimationDidStopSelector: #selector(pushAnimationDidStop:finished:context:)];
control.view.center = self.view.center;
self.view.center = CGPointMake(self.view.center.x - CGRectGetWidth(self.view.frame), self.view.center.y);
[UIView commitAnimations];
- (void) pushAnimationDidStop: (NSString *) animationID finished: (NSNumber *) finished context: (void *) context
{
[self.view removeFromSuperview];
}
and in the second view controller I did the same but change the animation direction.
My real problem is when I run this code, every time I create an object of the another class and it is not deallocating. By profiling this code, found that the every time the object is alive and it is not dying and the memory allocation get increasing.
You can run the allocation instrument with reference count recording enabled. Then evaluate where the offset differs from your expectations, based on that data.
Related
I am working on one iPhone application in which I implemented one animation UIViewAnimationTransitionFlipFromLeft. Here my application works fine in the Portrait mode. It is doing the same animation as specified means Flip from Left to Right.
But when I am doing this UIViewAnimationTransitionFlipFromLeft in landscape mode then it is not rotating from left to right. Instead of it is rotating from top to bottom. This is really critical issue. Can you help me out to solve this.
The code I am using for iPhone application to rotate the view is as follows:
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.view.window cache:NO];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[UIView commitAnimations];
[self.navigationController pushViewController:objSecond animated:YES];
Thanks,
Best Regards,
Gurpritsingh Saini
If you are using iOS 4.0 or later, the following will do exactly what you want (I just tested it out to make sure)
NewView *myNewView = [[NewView alloc] initWith.....];
[UIView transitionFromView:self.view toView:myNewView.view duration:1 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
//[self.navigationController pushViewController:myNewView animated:NO];
[myNewView release];
EDIT: I'm changing the above code a bit (nothing new, just commenting out the navigation controller because it's not necessary for this).
So there are several ways to go about this (as far as keeping track of the next view), but this is the easiest I can think of. You can already switch from view 1 to 2, so I'm going to explain how to get from 2 to 10 (or however many you need).
Basically, the view transition lasts too long for viewDidLoad to catch a call to go to the next view. So what we need to do is set up a timer that waits and sends a method to switch at a later time. So this is the code you would see in view 2 (and 3 and 4, etc.).
- (void)viewDidLoad {
// this gets called before animation finishes, so wait;
self.navigationController.delegate = self;
// you will need to set the delegate so it can take control of the views to swap them;
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(switchView) userInfo:nil repeats:NO];
}
I only wait 1 second until I call the switch method, but if you are loading a lot into your views, you may want to wait a bit longer. 1.5 seconds should be more than enough, but you can play around with that to see where it works and doesn't work.
Next, you have to call the next view in the switchView method.
- (void)switchView {
NextView *myNextView = [[NextView alloc] initWith ... ];
[UIView transitionFromView:self.view toView:nextView.view duration:1 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
[nextView release];
}
This worked perfectly for me. Just to make sure I was getting new views, I assigned tags to each view and added UILabels as subviews in each view's viewDidLoad method and each showed the number of its view. So hopefully this is what you needed. I'm sure you have more complex things you will need to do, but this will give you the animation and logic you need to get the look you want. (on a side note, viewDidAppear doesn't seem to get called when doing this, so it might be necessary to call it manually from viewDidLoad if you really need to use it, but otherwise it works fine)
You will have to manually add a transformation to your view; the flip transformation always operates as if the view controller were in portrait orientation.
Note that the context argument to +beginAnimations:context: is not meant to be a CGContextRef per se. You probably don't want to pass the current graphics context there. Pass NULL instead.
Try with this :
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = 0.0;
CGFloat endValue = M_PI;
rotateAnimation.fromValue = [NSNumber numberWithDouble:startValue];
rotateAnimation.toValue = [NSNumber numberWithDouble:endValue];
rotateAnimation.duration = 1.5;
[self.view.layer addAnimation:rotateAnimation forKey:#"rotate"];
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[UIView commitAnimations];
I think this will work out.
The Accepted answer by slev did not work for me, I got all sorts of errors after trying the code in my custom Segue. I found a method that not only works but is more precise as it doesn't require the use of a Timer. Here is MySegue.m:
#implementation FlipRightSegue
- (id)initWithIdentifier:(NSString *)iden source:(UIViewController *)sour destination:(UIViewController *)dest
{
self = [super initWithIdentifier:iden source:sour destination:dest];
return self;
}
- (void)perform
{
UIViewController *src = [self sourceViewController];
UIViewController *dst = [self destinationViewController];
//[UIView transitionFromView:src.view toView:dst.view duration:1 options:UIViewAnimationOptionTransitionFlipFromRight completion:nil];
//[UIView commitAnimations];
[UIView transitionWithView:src.navigationController.view duration:0.8 options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[src.navigationController pushViewController:dst animated:NO];
}
completion:NULL];
}
#end
I also have a second class for flips to the right.
Code was taken from this website: http://www.dailycode.info/Blog/post/2012/11/29/iOS-Custom-Flip-Segue-(portrait-and-landscape-layout)-xcode-4.aspx
I have 2 subviews on the stage (a splash screen, and the main screen) once the splash screen finishes playing its audio it calls a function called -(void)audioComplete which is suppose to fade out the splash screen, revealing the main screen. I can't seem to get that working.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.window addSubview:splashController.view];
[self.window addSubview:rootController.view];
[self.window makeKeyAndVisible];
return YES;
}
-(void)audioComplete{
NSLog(#"REMOVE FROM STAGE");
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:splashController.view];
splashController.view.alpha = 0.0;
[UIView commitAnimations];
[splashController release];
}
If I add NSLog(#"%#",[splashController.view superview]); in the audioComplete function I get (null), but not when I add it to the didFinishLaunchingWithOptions function.
Apple highly recommends against using splash screens. Instead, you should use an image called 'Default.png' in the root directory. This will get displayed while the application is launching and make it appear that your application is faster than it actually is. Apple could potentially reject your submission to the app store if you create your own loading screen.
Could be one of two things:
1) Seems like your rootControllerView is being added on top of the splashControllerView.
Maybe your animation is happening but you can't see it as your rootControllerView is blocking it.
Try reversing your order of addSubview.
2) Don't release your splash view in the same method you're using for animation. Wait for the animation to finish before releasing your view. You can do this by:
-(void)audioComplete
{
NSLog(#"REMOVE FROM STAGE");
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidFinishSelector:#selector(releaseView)];
splashController.view.alpha = 0.0;
[UIView commitAnimations];
}
-(void)releaseView
{
[splashController release];
}
Also, as a best practice for memory management, don't release splashControllerView directly.
What I do is release a view immediately after adding it as a subview (adding a subview increases it's retain count).
When I'm done with the subview, I simply call [subView removeFromSuperView] which reduces the retain count and makes it zero.
Simply put:
UIView *view = [[UIView alloc] init]; //retain count = 1
[self.view addSubview:view]; //retain count = 2
[view release]; //retain count = 1
//do stuff with the view
[view removeFromSuperview]; //retain count = 0;
I don't really see your question, because in your didFinishLaunchingWithOptions you're adding your view and it's really there! So you will receive an answer every time you call NSLog(#"%#",[splashController.view superview]); but in audioComplete you release your subView and it's away... After this you can't get access to the superview, because - you're subView is released...
I would use this:
-(void)audioComplete{
NSLog(#"REMOVE FROM STAGE");
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:splashController.view];
splashController.view.alpha = 0.0;
[UIView commitAnimations];
[splashController.view removeFromSuperview];
}
You'll get the same thing again, but you're subView is still there if you need something later.
For Fade in and Fade out images between splash and mainscreen. i suggest you could trying having two image in rootviewcontroller. made the fade in and fadeout animation between two UIIMageViews......
for fade in read out this sample tuts:
http://iosdevelopertips.com/user-interface/fade-transition-fade-images-in-and-out.html
during the animation, u can complete your audio streams
My code crashes with a bad access, when i have a view with tables. I have figured what the problem is, but looking for the right solution.
In View 1 - i have a button that creates and instance of view 2 and then releases it, something like this:
settingsScreen *settings = [[settingsScreen alloc] initWithNibName:#"settingsScreen" bundle:nil];
CGRect theFrame = settings.view.frame;
//theFrame.origin = CGPointMake(self.view.frame.size.width, 0);
theFrame.origin = CGPointMake(0,-(self.view.frame.size.height));
settings.view.frame = theFrame;
theFrame.origin = CGPointMake(0,0);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.8f];
settings.view.frame = theFrame;
[UIView commitAnimations];
[self.view addSubview:settings.view];
[settings release];
In view 2 there is table, where i am setting the datasource and delegate to self on viewDidLoad, but this crashes the app with exec_bad_access
If i remove [settings release] in View 1, everything is fine.
If i do not release the view, is'nt it wrong to leave it in the memory?
how can i get over this situation?
Thanks
you should use either pushViewController or presentModalViewController instead of adding a view of one Viewcontroller in another Viewcontroller and when you want to come back you can use popViewController or dismissModalViewController, this helps in memory management as well. Also use [NSBundle mainBundle] for bundle parameter in initWithNibName which finds proper xib in the bundle.
I am sure somehow your settings instance is being released somewhere in your code again and it makes your app unstable.
Sanniv's answer is correct. You should use push-pop concept of navigation controller or present the controller in a modal way.
(Visit: i) http://forums.macrumors.com/showthread.php?t=472236 & ii) http://www.iphonedevsdk.com/forum/iphone-sdk-development/3643-pushviewcontroller-versus-presentmodalviewcontroller.html for more info).
Now the error you're getting is because of your "settings" object is being used in animation which you're releasing before your animation gets finished. Thus, instead of releasing the object immediately, release in another method which would be called exactly after animation stops. Like following way:
settingsScreen *settings = [[settingsScreen alloc] initWithNibName:#"settingsScreen" bundle:nil];
CGRect theFrame = settings.view.frame;
//theFrame.origin = CGPointMake(self.view.frame.size.width, 0);
theFrame.origin = CGPointMake(0,-(self.view.frame.size.height));
settings.view.frame = theFrame;
theFrame.origin = CGPointMake(0,0);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.8f];
[UIView setAnimationDidStopSelector:#selector(releaseObjects)];
settings.view.frame = theFrame;
[self.view addSubview:settings.view];
[UIView commitAnimations];
In the "releaseObjects" objects method, you should release your settings object. And in a case, if your settings object is a local, do it autorelease like this way -
settingsScreen *settings = [[[settingsScreen alloc] initWithNibName:#"settingsScreen" bundle:nil] autorelease];
I am using UIView's animation features to swim one image of a fish onto the screen and when there already exists a fish in the middle of the screen, swim it off and swim a new one on. The selection of the fish is sent from a separate tableview controller.
To accomplish this, I m keeping two instance variables of UIImageView and exchanging self.fishViewCurrent for self.fishViewNext once the animation is completed (see code below).
My problem is when the user presses on new table cells quickly, before the next fish has had a chance to arrive. My quick solution is to delay a new message that will add the fish and just do nothing at the moment. This works for 2 quick touches, but not more. I am looking for ideas if anyone knows how I can improve the code below, so that the animation is smooth and you can click on whatever while the final displayed image is the one last clicked. Here re the important methods:
Creating the first image view:
- (void) createFish {
self.fishViewCurrent = [[[UIImageView alloc] init] autorelease];
self.fishViewCurrent.contentMode = UIViewContentModeScaleAspectFit;
self.fishViewCurrent.bounds = CGRectMake(0.0f, 0.0f, kFishWidth, kFishHeight);
self.fishViewCurrent.center = CGPointMake(-kFishWidth, kFishYPos); // off the screen to the left
[self addSubview:self.fishViewCurrent];
}
Public method called when a tableview cell is selected:
- (void) addFish:(UIImage *)fish {
if (self.fishViewNext) {
[self performSelector:#selector(addFish:) withObject:(UIImage *)fish afterDelay:1.0];
return;
}
self.fishViewNext = [[[UIImageView alloc] initWithImage:fish] autorelease];
self.fishViewNext.contentMode = UIViewContentModeScaleAspectFit;
self.fishViewNext.bounds = CGRectMake(0.0f, 0.0f, kFishWidth, kFishHeight);
self.fishViewNext.center = CGPointMake(self.bounds.size.width + kFishWidth, kFishYPos); // off the screen right
[self addSubview:self.fishViewNext];
[UIView beginAnimations:#"swimFish" context:nil];
[UIView setAnimationDuration:2.0f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(fishSwamOffHandler:finished:context:)];
[UIView setAnimationRepeatAutoreverses:NO];
self.fishViewNext.center = CGPointMake(self.center.x, kFishYPos); // center
self.fishViewCurrent.center = CGPointMake(-kFishWidth, kFishYPos); // exit stage left
[UIView commitAnimations];
}
Handler to switch the 'next' fish (UIImageView) to the 'current':
- (void)fishSwamOffHandler:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[self.fishViewCurrent removeFromSuperview];
self.fishViewCurrent = self.fishViewNext;
self.fishViewNext = nil;
}
Simplest thing would be to create an ivar BOOL, say _fishIsSwimming, and a _queuedFishView then change the beginning of addFish: to look like this:
- (void) addFish:(UIImage *)fish
{
// queue the most recent fish, if one was passed in
if(fish)
self.queuedFishView = [[[UIImageView alloc] initWithImage:fish] autorelease];
// If something is swimming, leave now
if(_fishIsSwimming)
return;
// If we get here and there's no queued fish, leave now
if(!self.queuedFishView)
return;
// We have a queued fish and nothing is swimming, so do the queued fish next
// and clear the queued fish
_fishIsSwimming = YES;
self.nextFishView = self.queuedFishView;
self.queuedFishView = nil;
// Then carry on as normal
}
Then in your animation completion callback, you do this:
-(void) fishSwamOffHandler:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context
{
// We're done animating, so ready to do another swimming fish
_fishIsSwimming = NO;
[self.fishViewCurrent removeFromSuperview];
self.fishViewCurrent = self.fishViewNext;
self.fishViewNext = nil;
// now call to add another fish with nil, so that it will use
// the queued fish if there is one and otherwise just early out
[self addFish:nil];
}
So what will happen is that you'll always maintain the most recently passed in fish in the queuedFishView, and then as soon as the animation is over you can drop back into addFish, and if there's a fish waiting to go then you send it. If not you just wait for the next one sent by a button press
I'm working on animating little subviews throughout my UI using a flip transition. To get the Flip transition to work correctly, I create a temporary UIView to provide the context, run the transition, and then need to clean up afterwards. But I'm having trouble figuring how to release the object without crashing the application. Here's the code block:
UIView *tempContainer = [UIView alloc];
tempContainer = [self.view viewWithTag:700];
[UIView transitionWithView:tempContainer
duration:2
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[[[tempContainer subviews] objectAtIndex:0] removeFromSuperview];
[tempContainer addSubview:newImageView];
[newImageView release];
}
completion:^(BOOL finished){
[tempContainer release]; //Crashes app
}];
I'm using block-based animation techniques for iOS4. The problem is that my tempContainer is definitely leaking, but if I release or autorelease it in the completion block, my app crashes, and I do it after the [UIView transition...] message, it crashes. What's the best way to refactor this so I don't leak out my memory? (I have 30 of these little things to do.)
It leaks because after you've +alloc-ed
UIView *tempContainer = [UIView alloc];
you immediately override it.
tempContainer = [self.view viewWithTag:700];
It crashes when you -release because you don't own the overriding view ([self.view viewWithTag:700]).