I'm currently working on an app that builds a stack of controllers depending on the user.
Basically, I have a UIViewController that has a UIButton leading to another UIView Controller; that has a button leading to another view controller and so on. The view controllers are pushed so that when the user always press the button, I get a stack of multiple view controllers. The views are popped whenever the user wants to go back to the previous view controller.
Everything is working well (push and pop). However, at random instances, the app would crash. I noticed that it happens when there are already a large amount of views pushed, and I suspect that it can be a memory issue.
My question is, other than pushing the view controllers, is there an alternative so that I can avoid stacked views? Could it also be that the crash is not because of the stacked views but because I'm just missing something out? There is no error presented in the logs so I can't find out what's happening and I'm also new to iOS development.
Thank you very much!
Edit 1: There is no error in the logs but when the app crashes, there is this message:
Thread 1: EXC_BAD_ACCESS(code = 1, address = 0xd000000c)
Edit 2: This is how I am pushing the controller:
CustomController *custom = [self.storyboard instantiateViewControllerWithIdentifier:#"Custom"];
[self.navigationController pushViewController:custom animated:YES];
And this is how I popped it when the back button is pressed:
[self.navigationController popViewControllerAnimated:YES];
Edit 3: After enabling zombie objects in the scheme, I started to get this messages after multiple push and pop:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Unbalanced calls to begin/end appearance transitions for
Do those messages say that the problem is actually on pushing the controller with animations? Thanks everyone!
Edit 4: I'll try to revise the question to make it more descriptive
This is my setup:
Controller A displays icons that corresponds to different places. You can click on the icon to push Controller B and display details for Location A.
Controller B displays information about Location A, with a button to show Controller A that now displays icons close to location of Location A. Now, you can again click an icon, say for Location B, and display details and so on.
When the user presses the back button, it should display the previous view controller. This is why I used push and pop. Is there a better way to handle this process. Thanks again!
My suggestion is: check if Zombie Objects is enabled and use the instrument "Allocations" to see if your application have, in fact, memory issues. With the informations provided by these tools, you can figure out what is happening with your application and work on it.
Tell me if you need help.
Good luck!
When push or pop, you should turn off animation. I think this causes crash when animation does not finish.
Push: self.navigationController pushViewController:custom animated:NO];
Pop: [self.navigationController popViewControllerAnimated:NO];
My guess is that you push multiple view controllers with animations - this may be a root cause of error. If you push more than one view controller you should animate only LAST push, say:
VC1 *vc1 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC1"];
[self.navigationController pushViewController:vc1 animated:NO];
VC2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC2"];
[self.navigationController pushViewController:vc1 animated:NO];
VC3 *vc3 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC3"];
[self.navigationController pushViewController:vc1 animated:YES];
However, i hardly imagine the situation where the multiple push would be necessary - i think it always leads to bad UX.
Related
I'm using Xcode 5 with storyboards and I should do something like this:
ViewController with a Start button that launches IntermediateViewController
IntermediateViewController that does an activity and then returns the value to the ViewController.
For the passage ViewController->IntermediateViewController I've set the start button to trigger a push segue. Actions are done and this part seems ok.
Now I have to go back to ViewController passing a string I got in IntermediateViewController methods.
If I use:
ViewController *viewController=[self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"viewController"];
viewController.passedString=_mystring;
[self.navigationController pushViewController:viewController animated:NO];
I get this error:
"Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted"
Is there a way to retrieve my viewController first instance through its identifier or any other solution that will lead the app back to viewController setting also its variable?
Thanks in advance
Even if it's not the exact answer to the question, I solved using this steps:
1) checked in storyboards that each element triggers only one action or segue, not both.
2) calling second view using:
[self.navigationController pushViewController:intermediateViewController animated:NO];
2) going back to previous view.
[self.navigationController popViewControllerAnimated:YES];
3) doing this before popping:
pass string between controllers
Body:
I am getting a Memory warning in my app, after which the UI stops responding. And, in the XCode logs I do see ViewController being Unloaded message.
I am afraid it is because I am not handling the transitions between the views properly and which is causing this memory issue.
Brief description of the ViewControllers(VC) I have and how I perform the transition:
I have 1 main/home VC which is the start of the main workflow of my app.
And from all other VCs, I have links to come back to the Home VC.
So, instead of having Segues from all the VCs to the 1st one, I use the following way:
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil];
HomeViewController *homeViewController = [sb instantiateViewControllerWithIdentifier:#"HomeView"];
[self presentViewController:homeViewController animated:YES completion:nil];
Intention was to just avoid having so many Segues from all the Views connecting to the Home View.
I feel this way of transition is causing the memory issue. Same View is getting added to the stack several times and causing the issue.
I am no expert of iOS, so any help/suggestion will be of great help to me.
It looks like the way you have it you're creating an entirely new ViewController everytime you intend to transition back to the HomeView. This is a very bad idea because everytime you make a transition you're putting a new view controller on the stack, rather than using the original ViewController (which you should be doing).
So as you keep making a transition, you're allocating new memory which eventually causes a memory warning then causes a stack overflow making your app crash.
HomeViewController should be presenting other view controllers using this method presentViewController:animated:completion: and dismissViewControllerAnimated:completion: or something similar in order to perform transitions if you don't want to use segues.
Please read this apple documentation:
https://developer.apple.com/library/ios/featuredarticles/viewcontrollerpgforiphoneos/ModalViewControllers/ModalViewControllers.html
Alright I'm really failing to see what the big win in having Navigation Controllers wrap my views and trying to drag segues on my Storyboards are. Sure you get the benefit of iOS automatically adding a back button as you push/pop views off the stack, but for anything past a simple app, I don't even care for that!
My goal is simply to have 1 view actually represent a given page. Then just programatically move to another view using:
ViewController *vc = [storyBoard instantiateViewControllerWithIdentifier:#"ViewController"];
[self presentViewController:modalYearPickerViewController animated:NO completion:nil];
or
ViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
[self.navigationController pushViewController:vc animated:YES];
I realize that in order to push with the second example I'd need the view to be wrapped in a Navigation View Controller, however using one of these methods prevents my from ever having to create "segues" which I find hard to manage. Half the time when I setup a segue iOS throws an error saying it cannot perform or the segue doesn't exist.
A lot of the time I'm either hiding back buttons or on the fly changing where "back/return" may drive the user based on previous actions or screens.
Anything massive I'm missing here? I am fairly new to iOS but I come from the web world where I'm used to being able to navigate around screens with ease.
Anyone have any thoughts?
EDIT: Here is a dead simple example solution. When I get to the "details" page I want to have a back button to the home page instead of the modal.
http://andrewherrick.com/spike/nav_ios.zip
From your diagram, I'd expect:
At the root, you have a navigation controller which contains the Artists. Artists has a modal segue to New Artist (well, to a nav controller containing it) and a push segue to Artist Songs.
New Artist is the root of a navigation controller and has a push segue to Artist Songs. New Artist has a Cancel and a Done button in the navigation bar.
Artist Songs should be configurable (it could determine this itself), such that it has a back button (always based on this model, and automatically handled) and a Done button when it is pushed from New Artist (which completes the save and dismisses the modal, thus taking the user back to Artists).
All of the above is a achievable either with segues or direct instantiation and transition from code, which you use is a matter of personal preference.
I have an screen (My Profile) which can be accessed from two paths:
Login -> Content -> Profile
Login -> Register -> Profile.
In both paths, view's are shown with:
[self.navigationController pushViewController...];
But my problem is, after the user registers and completes his profile, it should go "Back" to the Content view. (Obviously it won't work with navigation controller stack, since Content isn't in the navigation controller).
My question is, what suggestions do you have?
PS: I know this isn't an actual question, but I've been thinking about this for a few hours now and I didn't come up with anything. Maybe some of you have had to deal with similar cases.
Edit: Basically the question can be generalised to:
How do you deal with a circular application flow?
Edit: I've solved this by pushing from Register to Content and then Profile in viewWillAppeare without animation (so what I need is in the stack), but I'm still interested in dealing with circular application flows.
My suggestion would by to change [UINavigationController viewControllers].
So after you end your registration you can do something like
ContentVC *content = [[ContentVC alloc] init];
[self.navigationController setViewControllers:[NSArray arrayWithObject:content]];
[self.navigationController popToRootViewControllerAnimated:YES];
Push Contentview controller after profile complated in second case , while in first case you have already pushing it.
1)Make your Login viewController the rootController of the a UINavigationController.
2)From Login you can Push Content ViewController
3)From Content VC you can push to Profile VC.
Now if you want to get back to Content from profile do this:
[self.navigationController popViewControllerAnimated:YES];
And if you want o get back from Profile directly to Login do this:
[self.navigationController popToRootViewControllerAnimated:YES];
If you do not want the navigation bar you can hide the bar.
Update
Take a different approach, other than the navigation controller stack:
1) Make a controller class with a 2 functions:
-(void)loadViewControllerWithIndex:(int)index;
and
-(void)unLoadCurentViewController;
2) You can call these functions and load and unload the view controllers from a this controller class.
3) So you initially load this class and import file in this class of other view controllers. You can take a UIViewController object topViewController, so that you can you can keep a track on which controller is currently displayed and it will help when you want to unload a controller.
4) In load controller with Index function you can add the controller view and in unload you may remove them.
Simplified question: Is there any way to restart the navigationController of an application?. I'm trying to force the application to get his initial appearance.
Long explanation
I've a pet project in iOS and I have a weird problem with the interface that I'd like to solve. I'd like to understand also the mechanics behind this behavior.
I've a simple welcome view, wich shows the splash screen of the application. After that, thread goes to sleep state for 1.5 seconds.
[NSThread sleepForTimeInterval: 1.5];
Then, I'm showing an advertisement view:
AdController *ad = [[AdController alloc] initWithNibName:nil bundle:nil];
[self.navigationController presentModalViewController: ad animated:YES];
[ad release];
And that's all the logic behind. After that, other controllers are pushed without incidence. I want to achieve that, if at any moment the user makes the application go to background (pushing the iPhone/iPad button) then all the controllers must disappear from the stack via pop. In order to get it I'm using applicationDidBecomeActive event from the delegate. The code is the following:
[self.navigationController dismissModalViewControllerAnimated:NO];
[self.navigationController popToRootViewControllerAnimated:YES];
This is driving to some weird visual behaviours. Depending of the moment that the user choose to push de button the transition to the first view is visible. In other cases the ad view is still present, so it is dismissed and then appears the splash screen.
It will be great if there is some way to reset this first controller (splash screen), in order to get all the transitions working as the first time. I've thought about pop it from the navigation controller and the reload another one, allocating again, but it seems a bit complicated.
Is there any simple way to achieve that?
Important Edit: If the user forces repeatedly the application to go background then these exceptions are thrown:
nested pop animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
The easiest way that I've found is to add to the plist file a new row with key "Application does not run in background" and with value YES.
Forces the application to be completely closed and unloaded from memory when the user pushes the button.