I am new to Objective C. I am working on my first app.
- It basically consists of 2 view controllers, and I use modal segue to switch between them. The main vc is a menu that loads up the 2nd vc with different attributes for each menu item.
- I noticed that the memory keeps increasing when I switch from one vc to the other. This was my attempt to solve the issue but it doesn't make a difference and it doesn't look clean.
-(void)viewDidDisappear:(BOOL)animated{
ViewController *me = self;
me = nil;
}
What is the best practice to handle memory in a case like this?
The problem is that you are using a modal segue in both directions. Don't do that. You are simply creating a new view controller each time: you have view controllers piling up on top of each other. The opposite of a modal segue (which is actually presentViewController:animated:, after all) is not another modal segue; it is dismissViewControllerAnimated: (or, with some added complexity, an unwind segue).
Related
I have an iPad app which has a lot of screens and a lot of segue options.
At the moment, I am simply using performSegueWithIdentifier to initiate these segues, and my fear is that I'm taking up a lot of memory as the user performs more and more segues.
I've seen that people recommend using the function popToRootViewControllerAnimated: if using a UINavigationController, but the problem is that I'm not using one.
How can I stop the number of VC's proliferating?
The way the app works, the user does constantly return to the root VC - effectively a search screen. So if I could clear the stack of VC's when such a segue is needed, that would solve my issue I think, but I have no idea how to go about this.
Thanks for any suggestions.
When you are using segues the flow moves backwards and forwards. When the user moves backwards (ie presses "back") then it will not push to a new VC but it will pop to a VC that already existed. When you pop, the current VC is removed from the stack and memory.
If you have segues to move backwards in the flow then this is wrong. You only need segues to move forward.
A PROPER PREPARE FOR SEGUE
In prepare for segue you should never create your own view controllers and push to them. The storyboard is there to do all of this for you.
A proper prepareForSegue method should look something like this...
- (void)prepareForSegue:(UIStoryBoardSegue*)segue
{
if([segue.identifier isEqualToString:"SomeSegue"])
{
MyNewViewController *controller = segue.destinationViewController;
controller.someProperty = "some value to pass in";
}
}
That is all you need. Note that you only need this if you intend to pass some information to the new view controller. If you are not passing anything forward then you don't need this method at all.
When the method ends the new VC will get pushed onto the screen by the storyboard file.
UNWIND SEGUES
If you have a random flow (like in your comment) then you can use unwind segues to achieve this.
In you 'A' view controller have a function like...
- (IBAction)someUnwindAction:(UIStoryboardSegue*)sender
{
//some action to run when unwinding.
}
It needs to receive a UIStoryboardSegue object. If set up as an IBAction you can also access it from Interface Builder.
Now when you want to go A > B > C > B > A then just used the standard push and pop (from the segue and back button).
When you want to go A > B > C > A then you can use the unwind segue from controller C.
If you have a cancel button or something in controller C and this is in the Interface Builder and this should take you back to controller A. Then in the Interface Builder underneath controller C you will have a little green square with a door and an arrow pointing out of it. Just point the action of the cancel button to this symbol and choose "someUnwindAction". (Note, unwindAction is in A, button is in C.) XCode then uses this to pop you all the way back to A and deals with removing any memory and stuff. If you want you can send additional information back to A too.
If you want to access this unwind segue from C programmatically then you can run...
[self performSegueWithIdentifier:"someUnwindAction" sender:nil];
This will also pop back to A.
imho I don't see any issues with using segues, it is much more simple than anything else. If you worry about memory consumed then just profile your app and see how much it eats and how often your "memory pressure handler" is called.
ok I believe I've figured this out. It appears to work.
Since I keep returning to a common VC, I am placing code in the places where I want to segue back to this 'root' VC to clear the VC stack.
I still perform the actual segue in my normal code
[self performSegueWithIdentifier:#"editBackToSearch" sender:self];
However in the 'prepareForSegue:' method, I no longer create a new VC object, instead I perform any work I have outstanding (save my data), and then clear the VC stack with the following code.
UIViewController *vc = self;
while ([vc presentingViewController] != NULL)
{
vc = [vc presentingViewController];
}
[vc dismissViewControllerAnimated:NO completion:nil];
It appears to run smoothly and has the desired impact (confirmed via the profiler) of releasing the memory that these VC's sucked up.
One last comment - for some reason I could not animate the dismissal command, this appeared to trigger an infinite loop and resulted with an EXC_BAD_ACCESS. With animation set to NO, it works great though.
Thanks for your help.
Swift 3: this is to prevent memory leaks in Swift 3 & 4
#IBAction func entryViewSelected(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
As someone who usually used separate xibs in the past I thought I'd give storyboard a go as it seemed a lot simpler to use and much easier to develop with. I've been writing an application where the essential set up is this:
At the top of all this is a UINavigationController (first level). Then I have Multiple UIViewControllers (second level) with buttons in them which you can tap to switch between the second level UIViewControllers.
However a problem occurs when I start switching between the second level UIViewControllers. I first thought this was an initialisation problem with the NSMutableArrays because in my code I have a NSTimer set to loop periodically and found when I set a breakpoint during it, when I went forward to the next timer tick event there appeared to be different instances of the same NSMutableArrays and it seemed a gamble to try and insert new values into these array with it sometimes working, sometimes not (as it may or may not insert into the correct instance).
Then, looking at the memory usage under Debug Navigator I found the issue. Each time I "switched" between the UIViewControllers a new UIViewController was being initiated, along with all new variables.
The code I am using to switch between them is
-(void) perform {
[[[self sourceViewController] navigationController] pushViewController:[self destinationViewController] animated:NO];
}
Or essentially a push segue transition. This also explains why when I tried to switch back to my view, the data on that view was lost as it is a complete new view.
Does anyone know how to switch between multiple ones of these UIViewControllers (say 5) essentially like a UITabViewController would except without the tab bar being present?
First option you can do this: You can use a tabbarcontroller for switching viewcontroller and hidden the tabbar. Then on buttonclick setthe tabbar index.
Second option you can do this: Create one more view controller and in this viewcontroller subview the all switching viewController and when you want to switch viewcontroller just bring that viewcontroller view to front by delegate.
Do you need the navigation bar and other features provided by your top level navigation controller?
If not, you could use a UIPageViewController instead.
You set up all your second level view controllers and then just have to tell the page view controller which one to display.
If you implement the associated delegate methods, it will automatically provide swipe gestures to switch between them and nice animations to get them on and off screen.
You can also get it to put a UIPageControl at the bottom showing a dot for each VC with the dot for the current VC highlighted.
Here is my set up in storyboard.
I'm trying to use this code: (In an IBAction connected to the UIBarButtonItem in the last VC):
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarControl"];
tabControl.selectedIndex = 1;
[self presentViewController:tabControl animated:YES completion:nil];
}
to navigate from the last ViewController in this picture to that very first UINavigationController in the stack. I was informed that UINavigationControllers are not meant to be nested like this, and that only one UINavigationController should be necessary, but when I successfully remove(which I have done at least 5 times) the other three UINavigationControllers from the rest of the stack, I completely lose my UINavigationBars from the regular ViewControllers.
The problem with navigating the way that I currently am with my 'confirmClicked:' method, is that I create another instance of that first UIViewController in the hierarchy.
Should I:
A) Remove all the unnecessary UINavigationControllera in the view hierarchy, then use 'popToRootViewController:' to correctly navigate from the last VC to the first?
B) Try to navigate another way from the last VC to the first VC, keeping the extra UINavigationControllers in the view hierarchy?
Also, if I remove the Navigation Controllers, how will I keep my navigation bars in the UIViewControllers, since they have been disappearing in the past when removing the Navigation Controllers?
Sorry in advance for the many questions but I have been stuck for a while.
OK
First thing
Remove all those navigation controllers. You only need one.
Second
The code in that IBAction is creating a brand new instance of the TabBarController and placing it over the current stack (this is how to get memory problems).
Third
What you probably want is either an unwind segue (possibly) or code something like this...
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.tabBarController setSelectedIndex:1];
}
Here is my issue.
The App I am creating has non-linear navigation.
So I am implementing my own back button and doing my own navigation.
However, I am wondering how I should be presenting the next view.
If my navigation was linear, I could do:
-(IBAction)btnBackPressed:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
However that will not work for me since pressing back may not necessarily bring you back to the previous view.
Therefore I am thinking of using for example:
AddTaskViewController *add = [[AddTaskViewController alloc] init];
[self presentViewController:add animated:YES completion:nil];
The reason I am not using Storyboard is because all my UI is made programmatically in code.
The worry I have with this is that I think presentViewController will just push the new view on top of a stack. Thus, if the user presses back, forth, back forth, he will eventually run out of memory.
Given my circumstance that I need non-linear navigation and all my UI is created in code, what should I use to present the next view without wasting memory?
Thanks
If you are still interested in a clean solution using UINavigationController, consider this design.
Situation: Let A and B be types of view controllers. A is the root, and B is a detail view which can push or pop to other B controllers.
Goal: We want to delete any B controllers that are not adjacent to the currently presented view controller, but maintain the hierarchy so we can recreate the views when necessary. Thus, the maximum hierarchy the navigation controller will know about is A--B--B.
Design: Make A the navigation controller's delegate. Give it an array of model objects which represent B controllers enough to recreate the views from them. Add to this array whenever a B controller is pushed, which A will know about from the navigation controller delegate methods. Remove objects from the stack when a B controller is popped.
On pushing a B controller, the A controller will take the navigation controller's view controller stack and (if it exists) remove the B controller directly before the one that was displayed before the push. On popping a B controller, the A controller will (if it exists) recreate the B controller directly before the destination controller and insert it in the stack.
Example: Let's say A has kept track of a hierarchy like this: A--B1--B2--B3--B4. By the system outlined above, the navigation controller only knows about A--B3--B4. When the user pops B4, the A controller will be notified and recreate B2, inserting it before B3. Thus, the new hierarchy is A--B2--B3. When B5 is pushed from B3, B2 is removed to produce A--B3--B5.
I realize this is a fairly complicated system to implement, so I would only recommend it if you had a large number of controllers and required the amenities of UINavigationController. Another solution that occurs to me is UIPageViewController, which allows you to provide view controllers on the fly.
Hope this helps!
Yes you are correct that the user will eventually run out of memory if you keep presenting Modal view controllers using
[self presentViewController:add animated:YES completion:nil];
and
[self dismissViewControllerAnimated:YES completion:nil];
will only dismiss the top most viewController being displayed.
Instead you would be better off building your own container view controller and handle your own navigation. For more information read the "Implementing a Container View Controller" section in the UIViewController Apple documentation.
https://developer.apple.com/library/ios/Documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html
I have an app that has the following basic layout, please understand I have done a lot of programming, but I am relatively new to IOS and am yet to wrap my head around the Storyboards/segues properly yet.
Effectively, my app has the following screens:
WelcomeViewConroller ---ModalSeque--> MenuViewController --modalSegue---> newProjectVC || loadprojectVC ---modalSegue-->ProjectScreenVC.
From the project the screen, the user can return to the menuVC screen.
Now, I understand that every segue creates a new instance of a view controller, which is great, I want this to happen, however, when I segue back from my ProjectScreen, and then reenter it again, I get a huge memory leak and very strange behaviour.
I understand that I need to dismiss my View controllers, especially my ProjectScreen when I leave it, however, I can not get this to happen, no matter what I try.
Any help would be greatly appreciated.
In How should I chain viewcontrollers in xcode storyboard? I enumerate a series of ways of going back multiple scenes in a storyboard. But in short, the two easiest options are:
Unwind segues: If only supporting iOS 6 or higher, use unwind segues. So, for example, in your main menu's view controller, implement a unwind segue:
- (IBAction)gotoMainMenu:(UIStoryboardSegue *)segue
{
// if you need to do anything when you return to the main menu, do it here
}
Also make sure to define that in the main menu's .h. Then control+drag from the button that you want the segue to the "exit" button in the panel below the scene, and choose the "gotoMainMenu" option:
Navigation controller: If you need iOS 5 support, then just use a navigation controller and replace the modal segues with push segues. Then you can use popToViewController or popToRootViewControllerAnimated to jump back multiple scenes. If you don't want to show the navigation bar, then select the navigation controller in your storyboard, and in the attributes inspector, uncheck "Shows Navigation Bar":
In this scenario, I actually think it's easiest to make sure your menu scene is the root (and have it do a little detour to the welcome screen, like I discuss in point 4 of that other answer), in which case you can just call popToRootViewController whenever you want to return to the main menu. But, if the main menu is not the root view controller, and you want to pop back to it, you can either pass a point menu controller from scene to scene, or you can have subsequently presented view controllers do something like the following when they want to get back to the main menu:
for (UIViewController *controller in [self.navigationController viewControllers])
{
if ([controller isKindOfClass:[MenuViewController class]])
{
[self.navigationController popToViewController:controller animated:YES];
break;
}
}
Elsewhere on Stack Overflow, you'll see people contemplating ways to nest calls to dismissViewControllerAnimated, or other variations like that. I personally think that navigation controllers and unwind segues are far easier and more elegant.