I have a UIView in my class (besides the original view) made in interface builder.
#interface TimeLineGrid : UIViewController {
UIView *toggleView;
}
#property (nonatomic, retain) IBOutlet UIView *toggleView;
I have synthesized it as well. I have implemented a swipe gesture so that when swiped up, the toggle view is added and when swiped down the toggle view is removed.
-(void)swipedUp {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2.0f];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[self.view addSubview:self.toggleView];
[UIView commitAnimations];
}
-(void)swipedDown {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2.0f];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
[self.toggleView removeFromSuperview];
[UIView commitAnimations];
}
It works fine when I swipe up once and when I swipe down after that. But when i swipe up once again, it crashes with EXC_BAD_ACCESS error. I know this has something to do with the retain count increasing when I addSubview and reducing when I removeSubview. Can someone shed more light on this? How do I achieve this toggle?
EDIT:
My view hierarchy is as follows:
->UIView (toggleView)
->UIView (mainView to which toggleView is being added)
-->UIToolBar
Most likely, the view is being released when you call [self.toggleView removeFromSuperview];. I recommend that you restructure your code a little.
Since you are creating the view in IB, declare it in the header file as
IBOutlet UIView *toggleView;
Do not include the property call or the synthesize. Since you are not setting or getting the toggleView, just refer to it by the pointer.
In the implementation, set the pointer to nil in viewDidUnload and release it in dealloc. Then, adjust your toggle methods as follows:
-(void)swipedUp {
if (![toggleView superview]) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2.0f];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[self.view addSubview:toggleView];
[UIView commitAnimations];
}
}
-(void)swipedDown {
if ([toggleView superview]) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2.0f];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
[toggleView removeFromSuperview];
[UIView commitAnimations];
}
}
Here, the if clause will catch the event that the user swipes up twice in a row and not respond to the second swipe. Notice also that this uses toggleView in place of self.toggleView.
This should resolve the problem.
Could you please apply such changes and tell which UIView is exactly has proble.
Open XCode/Product/Edit Scheme...
Move to Environment Variables and then click the plus to add variable
Add variable NSZombieEnables - value: YES
Add variable MallocStackLoggingNoCompact - value: 1
After starting the application, you will see, which exactly object was disposed, when you call it.
I suggest making the views you want to show/hide, be both subviews of a container view. Then, in your animation block you first remove a view that dissapears and add the view that you want to show. For example:
#interface TimeLineGrid : UIViewController {
// declare the views you want to flip between
UIView *mainView;
UIView *toggleView;
}
#property (nonatomic, retain) IBOutlet UIView *mainView;
#property (nonatomic, retain) IBOutlet UIView *toggleView;
In your implementation:
// self.view is the container view (an instance of UIView).
// Initially, self.mainView is a subview of self.view
-(void)transitionFromView:(UIView *)fromView toView:(UIView *)toView withAnimation:(UIViewAnimationTransition)animation {
if ([toView superview]) {
// toView already shown
return;
}
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2.0f];
[UIView setAnimationTransition:animation forView:self.view cache:NO];
[self.view removeFromSuperview:fromView];
[self.view addSubview:toView];
[UIView commitAnimations];
}
-(void)swipedUp {
[self transitionFromView:self.mainView
toView:self.toggleView
withAnimation:UIViewAnimationTransitionFlipFromLeft];
}
-(void)swipedDown {
[self transitionFromView:self.toggleView
toView:self.mainView
withAnimation:UIViewAnimationTransitionFlipFromRight];
}
Note however, that use of the method setAnimationTransition:forView:cache: is now discouraged. Apple recommends using the UIView transitionWithView:duration:options:animations:completion: method (see the UIView reference).
I hope this helps!
I have made an application containing UITabbar and I got the same behavior but with UIButton.
That happens when you dont release the object on which you call removeFromSuperview, I have the same effect when I was removing a UIButton from my viewControllers view.
In my case I could still see the button and if I click on it, I get an exception like unrecognized selector send to object of type NSCFString.
But if I change the tab then return to my view controller where the button was showing previously after removing it from my view, then it was gone.
So my conclusion is that if you dont release the object on which your are calling removeFromSuperview then the app will show some unexpected behavior if you have the object as an instance variable of your class and added it to the view programmatically by addSubView method.
What are your options:-
You can call removeFromSuperview on a uiview or its subclass objects and call release on it or make it nil.
You can hide or unhide those objects using hidden property instead of removing them from superview.
(I haven't tried it exactly)You can create a UIView object in your class implementation and then add it as a subview and then assign it to your instance variable your class using dot operator and then release the previously created UIView object.
I dont know how my answer might help you but I think its good to share your experience with others.
Related
I have 2 view controller and I play a music with AVAudioPlayer in the first view controller.
I go to second view controller and stop the music :
[audioPlayer stop];
[UIView beginAnimations:#"Curl View" context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES];
[self.view addSubview:secondController.view];
[UIView commitAnimations];
When I go back to first view controller again, I want to play my music again but the code doesn't touch viewDidLoad again.
Here is my code to go back :
[UIView beginAnimations:#"Flipping View" context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:self.view.superview cache:YES];
[self.view removeFromSuperview];
[UIView commitAnimations];
Please share me some solutions or what the code execute after that..Thanks in advance
This is happens Only because of you are calling your Music Playing code from viewDidLoad
And viewDidLoad method calls Only One time For ViewController.
You should Call your code(Playing Music) from viewWillAppear instead of viewDidLoad method.
I suggest you should read UIViewControler Documentation.
Updated Answer:As you want to Play the Music File at the time of loading and Coming back to View .
There may some problem regarding the Performance.
Because you calls the Music file immediately loading time of view.
So View below Lines of explaination.
ViewDidLoad - This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView method. You usually override this method to perform additional initialization on views that were loaded from nib files.
ViewWillAppear:When this gets called, it means that the iPhone is already ready to show the UIView to the user, and anything heavy you do here will impact performance in a very visible manner (like animations being delayed,or playing music etc).
ViewDidAppear: Finally,This Notifies the view controller that its view was added to a view hierarchy.so this method is good enough for such task Like for example playing music file.Note-->you must call super at some point in your implementation.
Write you play code in viewWillAppear: , it is called when ever your view is appeared.
viewDidLoad: is called only once for a view controller
Update
Use these line to remove your second view
[super viewWillAppear:animated];
UIView beginAnimations:#"Flipping View" context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationEnded)];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:self.view.superview cache:YES];
[self.view removeFromSuperview];
[UIView commitAnimations];
And this method will be called when view is removed
- (void)animationEnded{
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/BGMCover2.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error; audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error]; audioPlayer.numberOfLoops = -1;
[audioPlayer play];
}
Before doing your application please get deep knowledge of UIViewController life cycle. You can follow the following link :
UIViewController Life Cycle
Hey all... I have a view controller (A) which on some action, alloc init's another view controller (B) and then adds B's view to its view as a subview. So now ViewController B's view is a subview of ViewController A. The problem I have is If I simply remove B's view from A it seems to still stick around for example. View B contains a web view, when I load a video on the webView, even after I remove the view from view Controller A's view I can still hear the video??
How can I destroy viewcontroller B and remove its subview from A? Im finding this tricky as I dont really push it onto a navigationcontroller's stack which I can just pop from... I hope this makes sense, if not please say and I will try and clarify.
Many thanks
Jules
-(void)showNewsWebView:(int)index {
NewsWebViewController *myWebView = [[[NewsWebViewController alloc] initWithNibName:#"NewsWebViewController" bundle:nil]autorelease];
//setup webview with request etc
[[self.view.superview superview] addSubview:myWebView.view];
myWebView.alpha = 0.
[UIView beginAnimations:#"test" context:nil];
[UIView setAnimationDuration:.3];
myWebView.view.alpha = 1.;
[UIView commitAnimations];
}
//called after delegate callback from webviewcontroller
- (void)newsWebViewDismissedView:(NewsWebViewController *)controller {
[UIView beginAnimations:#"test" context:nil];
[UIView setAnimationDuration:.3];
controller.view.alpha = 0.0;
[self performSelector:#selector(removeView:) withObject:controller.view afterDelay:.5];
[UIView commitAnimations];
}
-(void) removeView:(UIView *)view {
[view removeFromSuperview];
view = nil;
}
Does ViewController B really need to be a ViewController?
If you're adding subviews you should probably have B subclass UIView instead of UIViewController. Adding B's view as a subview essentially negates any advantage you'd have of B being a ViewController.
Anyway to answer your question. You might want to make viewcontroller B an ivar of A so that viewcontroller A can manage the memory of viewController B. Once you remove the view of B from A, you can release viewcontroller B from memory (I still don't support this as it sounds like ineffective code. You should probably state what you're aiming to do, and post some code as to how you're doing it so we can help you out better :) )
EDIT:
From your code seems like you should just be pushing and popping. Are you using MyWebViewController just to show a webpage? You might be better off using a simple UIWebView.
I also noticed something wrong in your animation code for setting alpha to 0. If you want some method to be executed after an animation ends you should use the following code:
//called after delegate callback from webviewcontroller
- (void)newsWebViewDismissedView:(NewsWebViewController *)controller {
[UIView beginAnimations:#"test" context:nil];
[UIView setAnimationDuration:.3];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(removeView)];
controller.view.alpha = 0.0;
[UIView commitAnimations];
}
ViewControllerB *vc = [[[ViewControllerB alloc] initWithNibName:#"SomeNib" bundle:nil] autorelease];
This should do the trick. Autoreleasing the view controller at the end of its initial allocation should tell the application to deallocate it after you remove the view from viewcontroller A since nothing else is holding a retain value on it. Though using a navigation controller might be an easier solution if you are willing to rework your code to push and pop the view instead
i have coded following in appDelegeate .m file .but i cant run presentModalViewController method.if i run [self.window addSubview:mview] ,it does not show the result..?any help
to go from one controller to another controller?here mtController is Navigationcontroller.
- (void)flip
{
MViewController *mview = [[MViewController alloc] init];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2.0];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
forView:window
cache:YES];
[mtController.view removeFromSuperview];
[self.window addSubview:mview];
// [self presentModalViewController:mailView animated:YES];
[UIView commitAnimations];
[mailView release]
}
If you want to add a NavigationController to a window you are supposed to call
[self.window addSubview:mview.view];
And if you want to go from one view in a NavigationController to another view, the correct thing to do would be to push the new ViewController.
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
In your code sample you create an object called mview, but then below in the presentModalViewController you reference mailView (and in the release). Is that a mistake?
After you add the mview.view to the window, is it the only view on the stack? If not, you might need to bring it to the front. Also, assuming the release statement at the bottom was meant to be [mview release] you're going to have another problem if you don't save/retain that view controller. I don't believe adding it to the window subviews retains it.
Have a simple iPhone app with a single UIViewController and two Views in one xib.
the first view is very simple with a button and upon button press the second more complex view is loaded via setting the view property on the controller.
what I would like is to animate the view swap (flip the views).
The samples I have seen all require having multiple view controllers and building a hierachy, but that would be overkill in this case, any suggestions?
Make sure you declare IBOutlets for the two views in your view controller I am assuming that in your xib you have a 'container view' that occupies the whole screen, and two views of the same size that you add to this contatiner (one for each side of your 'flip'):
//Inside your .h:
IBOutlet UIView *firstView;
IBOutlet UIView *secondView;
Make sure on initial load you have the firstView show up:
-(void) viewDidLoad {
NSAssert(firstView && seconView, #"Whoops: Are first View and Second View Wired in IB?");
[self.view addSubview: firstView]; //Lets make sure that the first view is shown
[secondView removeFromSuperview]; //Lets make sure that the second View is not shown at first
}
Then you can wire up a button like this, make sure the button is wired to this metod in IB:
-(IBAction) flipButtonPressed:(id) sender {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
if ([firstView superview]) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];
[firstView removeFromSuperview];
[self.view addSubview:secondView];
}
else if ([secondView superview]) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
[secondView removeFromSuperview];
[self.view addSubview:firstView];
}
[UIView commitAnimations];
}
In short: I want to have two fullscreen views, where I can switch between view A and view B. I know I could just use an Tab Bar Controller, but I dont want to. I want to see how this is done by hand, for learning what's going on under the hood.
I have an UIViewController that acts as an root controller:
#interface MyRootController : UIViewController {
IBOutlet UIView *contentView;
}
#property(nonatomic, retain) UIView *contentView;
#end
The contentView is hooked up to an UIView which I added as an subview to the "view" of the Nib. This has green color and I see it fullscreen. Works fine.
Then, I created two other View Controllers pretty much the same way. ViewControllerA and ViewControllerB. ViewControllerA has a blue background, ViewControllerB has a black background. Just to see which one is active.
So, in the implementation of myRootController, I do this:
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
ViewControllerA *vcA = [[ViewControllerA alloc] initWithNib];
[self.contentView addSubview:vcA.view];
[cvA release];
}
By the way, the -initWithNib method looks like this:
- (id)initWithNib { // Load the view nib
if (self = [super initWithNibName:#"ViewA" bundle:nil]) {
// do ivar initialization here, if needed
}
return self;
}
That works. I see the view from ViewControllerA when I start the app. But now the big question is: A View Controller typically has all those methods like:
(void)viewWillAppear:(BOOL)animated;
(void)viewDidDisappear:(BOOL)animated;
(void)viewDidLoad;
...and so on. Who or what, or how would those methods be called if I do it "my" way without a tab bar controller? I mean: If I allocate that ViewController's class and the view get's visible, would I have to take care about calling those methods? How does it know that viewWillAppear, viewDidDisappear, or viewDidLoad? I believe that the Tab Bar Controller has all this "cleverness" under the hood. Or am I wrong?
UPDATE: I've tested it. If I release the view controller (for example: ViewControllerA), I will get no log message on viewDidDisappear. Only when allocating and initializing the ViewControllerA, I get an viewDidLoad. But that's it. So all signs stand for the cleverness of UITabBarController now ;) and I have to figure out how to replicate that, right?
There's a nice example of switching views in Chapter 6 of Beginning iPhone Development. You can see the source code for it here:
http://iphonedevbook.com/
SwitchViewController has the code to change views programatically.
- (IBAction)switchViews:(id)sender
{
if (self.yellowViewController == nil)
{
YellowViewController *yellowController = [[YellowViewController alloc]
initWithNibName:#"YellowView" bundle:nil];
self.yellowViewController = yellowController;
[yellowController release];
}
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
UIViewController *coming = nil;
UIViewController *going = nil;
UIViewAnimationTransition transition;
if (self.blueViewController.view.superview == nil)
{
coming = blueViewController;
going = yellowViewController;
transition = UIViewAnimationTransitionFlipFromLeft;
}
else
{
coming = yellowViewController;
going = blueViewController;
transition = UIViewAnimationTransitionFlipFromRight;
}
[UIView setAnimationTransition: transition forView:self.view cache:YES];
[coming viewWillAppear:YES];
[going viewWillDisappear:YES];
[going.view removeFromSuperview];
[self.view insertSubview: coming.view atIndex:0];
[going viewDidDisappear:YES];
[coming viewDidAppear:YES];
[UIView commitAnimations];
}
You can begin from the simplest removeFromSuperview/insertSubview and add code to it little by little.
//SwitchViewController.h
#import
#class BlueViewController;
#class YellowViewController;
#interface SwitchViewController : UIViewController {
IBOutlet BlueViewController *blueViewController;
IBOutlet YellowViewController *yellowViewController;
}
- (IBAction)switchViews:(id)sender;
#property (nonatomic, retain) BlueViewController *blueViewController;
#property (nonatomic, retain) YellowViewController *yellowViewController;
#end
//1. remove yellow view and insert blue view
- (IBAction)switchViews:(id)sender {
if(self.blueViewController.view.superview == nil)
{
[yellowViewController.view removeFromSuperview];
[self.view insertSubview:blueViewController.view atIndex:0];
}
}
//2. appear=insert, disappear=remove
if(blueViewController.view.superview == nil)
{
[blueViewController viewWillAppear:YES];
[yellowViewController viewWillDisappear:YES];
[yellowViewController.view removeFromSuperview];
[self.view insertSubview:self.blueViewController.view atIndex:0];
[yellowViewController viewDidDisappear:YES];
[blueViewController viewDidAppear:YES];
}
//3. now add animation
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
//blue view will appear by flipping from right
if(blueViewController.view.superview == nil)
{
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight
forView:self.view cache:YES];
[blueViewController viewWillAppear:YES];
[yellowViewController viewWillDisappear:YES];
[yellowViewController.view removeFromSuperview];
[self.view insertSubview:self.blueViewController.view atIndex:0];
[yellowViewController viewDidDisappear:YES];
[blueViewController viewDidAppear:YES];
}
[UIView commitAnimations];
If I understand correctly, what you are trying to accomplish is pretty straightforward.
Just add a UINavigationController on your application delegate and do:
[navigationController pushView:vcA];
Delegates will be called accordingly:
(void)viewWillAppear:(BOOL)animated;
(void)viewDidDisappear:(BOOL)animated;
(void)viewDidLoad;
And when you want to pop the view and push another one:
[navigationController popViewControllerAnimated:true];
[navigationController pushView:vcB];
If you don't want the navigationController showing just use:
[navigationBar setHidden:YES];
Where navigationBar is the UINavigationBar corresponding to your UINavigationController.
This may be an old issue, but I recently came across the same problem and had a hard time finding something that worked. I wanted to switch between two complementary view controllers, but I wanted the switch to be animated (built in animations work fine), and I wanted it to be compatible with storyboards if possible.
For taking advantage of built-in transition, UIView's +transitionFromView:toView:duration:options:completion: method works beautifully. But, it only transitions between views, not view controllers.
For the transition to be between whole view controllers, not just views, creating a custom UIStoryboardSegue is the way to go. Whether or not you use storyboards, this approach lets you encapsulate the whole transition and manage the passing of relevant information from one view controller to the next. It only involves subclassing UIStoryboardSegue and overriding a single method, -perform.
For a reference implementation, see RAFlipReplaceSegue, the exact custom segue I put together using this approach. As a bonus, it also replaces the old view controller with the new if it is in a UINavigationController stack.