I have a UINavigationController and I'm trying to release from memory every UIViewController once another one is on top of the stack. I assign the viewControllers property of the UINavigationController to the new UIViewController and then pop into it. This way I always have just one UIViewController in stack. However, the memory keeps adding up every time I create a new UIViewController. Dealloc is called, but the memory usage remains the same.
You can download the example project HERE
FirstViewController.h
#import "SecondViewController.h"
#interface FirstViewController : UIViewController
-(IBAction)goToSecond:(id)sender;
#end
FirstViewController.m
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.navigationController.viewControllers);
}
-(void)goToSecond:(id)sender{
SecondViewController *secondVC = [[SecondViewController alloc]init];
[self.navigationController setViewControllers:#[secondVC]];
[self.navigationController popViewControllerAnimated:NO];
}
-(void)dealloc{
NSLog(#"FirstVC dealloc");
}
#end
SecondViewController.h
#import "FirstViewController.h"
#interface SecondViewController : UIViewController
-(IBAction)goToFirst:(id)sender;
#end
SecondViewController.m
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.navigationController.viewControllers);
}
-(void)goToFirst:(id)sender{
FirstViewController *firstVC = [[FirstViewController alloc]init];
[self.navigationController setViewControllers:#[firstVC]];
[self.navigationController popViewControllerAnimated:NO];
}
-(void)dealloc{
NSLog(#"SecondVC dealloc");
}
#end
Navigation controller should not be used as you intended.
You should call pushViewController and popViewController for present/dismiss your viewControllers.
If you have memory issues, try to release memory in didReceiveMemoryWarning callback
I'm not sure about the benefit of a uinavigationcontroller but anyway you could add this snippet on your .m of your uiviewcontrollers
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if (self.navigationController.viewControllers.count > 1) {
NSMutableArray *newViewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newViewControllers removeObject:[controllers objectAtIndex:1]];
self.navigationController.viewControllers = newViewControllers;
}
}
And instead of
[self.navigationController setViewControllers:#[firstVC]];
[self.navigationController popViewControllerAnimated:NO];
you can set
[self.navigationController pushViewController:[[FirstViewController alloc] init] animated:NO];
You are using pop to go further, but you need to use push if you want to go to the next ViewController.
-(void)goToSecond:(id)sender{
SecondViewController *secondVC = [[SecondViewController alloc]init];
[ self.navigationController pushViewController:secondVC animated:YES];
}
And in the SecondViewController to go back to your FirstViewController you should use pop
-(void)backToController
{
[self.navigationController popViewControllerAnimated:YES];
}
In your case
-(void)goToFirst:(id)sender
{
[self.navigationController popViewControllerAnimated:YES];
}
I uploaded a simple app to GitHub, with the fake navigation bar that i was talking about in my comment, hope it helps for your needs: https://github.com/yosihashamen/HelpersApps
Be ware that you must keep on "BaseViewController" alive at all times.
What I mean is something like this.
In your FirstViewController use presentViewController instead of push adding a new UINavigationController to the SecondViewController
-(void)goToSecond:(id)sender{
SecondViewController *secondVC = [[SecondViewController alloc]init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:secondVC];
[self presentViewController:nav animated:YES completion:NIL];
}
In the SecondViewController add an UIBarButtonItem to the Navigation bar
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleDone target:nil action:#selector(goToFirst:)];
self.navigationItem.backBarButtonItem = backButton;
And implement dismiss method.
-(void)goToFirst:(id)sender
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
Try out setViewControllers:animated:
This allows you to explicitly set the view controllers on the UINavigationController stack, like you are doing, and it will automatically handle the navigation animation without you having to call popViewControllerAnimated:
This is useful if you have a multi-view journey where you need to get rid of the screens that have been shown so far but maintain the navigation animation (eg. app demo on launch) or if you want to easily push multiple view controllers on the navigation stack at once.
Apple doc here: https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html#jumpTo_21
Related
My first view in the viewDidLoad has the following code
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Dialpad" bundle:nil];
self.vc = [sb instantiateViewControllerWithIdentifier:#"DialpadBoard"];
self.vc.delegate = self;
The header file contains the definition of the view controller
#property (nonatomic, retain) DialpadTableViewController *vc;
after catching an event the view loads a new modal view
- (void)handleEvent:(UIGestureRecognizer*)recognizer {
[self presentViewController:self.vc animated:YES completion:NULL];
}
The view also contains the method to dismiss the modal view:
- (void) dialpadControllerDidCancel:(SearchDialpadTableViewController *)controller {
[self dismissViewControllerAnimated:YES completion:nil];
}
The last method never gets called.
The problem is that the modal view when it is loaded has nil self.delegate. The new modal view is loaded from the storyboard as seen below. Why the delegate is nil? I cannot how a segue since the view is in another storyboard.
you can try this
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Dialpad" bundle:nil];
UINavigationController *nav= [sb instantiateViewControllerWithIdentifier:#"DialpadBoard"];
((SearchDialpadTableViewController *)[nav viewControllers][0]).delegate = self;
[self presentViewController:nav animated:YES completion:NULL];
Since we have the nav let's take the first controller and assign the delegate
the trick ;)
((SearchDialpadTableViewController *)[nav viewControllers][0]).delegate = self;
Does SearchDialpadTableViewController define a delegate protocol and does it have a property that is specific to that protocol?
The header for that file should look something like:
#protocol ViewControllerDelegateProtocol <NSObject>
- (void)dialpadControllerDidCancel:(SearchDialpadTableViewController *)controller;
#end
#interface ViewController : UIViewController
#property (weak, nonatomic) id<ViewControllerDelegateProtocol> searchPadDelegate;
#end
Then when the user taps Done which is I'm assuming what you're trying to do (send a delegate message when that happens). You would write something like
- (IBAction)donePressed:(id)sender
{
if ([self.searchPadDelegate respondsToSelector:#selector(dialpadControllerDidCancel)]) {
[self.searchPadDelegate dialpadControllerDidCancel:self];
}
}
you must be certain, that self.vc is a class, that you expected. It can be also NavigationController
line presentViewController crash app
#interface VVPassbook : NSObject
#property (nonatomic, retain) id delegate;
-(void)gotPassbookFromUrl:(NSURL *)passbookUrl;
#end
#import "VVPassbook.h"
#implementation VVPassbook
-(void)gotPassbookFromUrl:(NSURL *)passbookUrl
{
//present view controller to add the pass to the library
PKAddPassesViewController *vc = [[PKAddPassesViewController alloc] initWithPass:pass];
[vc setDelegate:(id)self.delegate];
[self.delegate presentViewController:vc animated:YES completion:nil];
}
#end
im call this method in AppDelegate.m in method
- (void)application:(UIApplication*)application didReceiveRemoteNotification:
(NSDictionary*)userInfo
{
VVPassbook *pass = [[VVPassbook alloc] init];
pass.delegate = self.window.rootViewController;
[pass gotPassbookFromUrl:MYURL ];
}
all times error
reason: 'Application tried to present a nil modal view controller on target <VVRightManuViewController: 0x15dd28460>.'
you should present a viewController from a UIViewController, i.e., self
(if self is a viewcontroller).
Note that you will get a "Attempt to present on whose view is not in the window hierarchy"-warning when self (this viewcontroller) is detached from the Main Window.
Solution: always use addChildViewController: to child viewcontrollers.
Why are you using self.delegate to push the view Controller,
simple write
[self presentViewController:vc animated:YES completion:nil];
in the place of
[self.delegate presentViewController:vc animated:YES completion:nil];
I'm using ICETutorial with cocoapods.
I'm using it in a SettingsViewController where you can view the tutorial in settings.
// SettingsViewController.m
Tutorial2ViewController *vc = [[Tutorial2ViewController alloc] init];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController pushViewController:vc animated:NO];
And Tutorial2ViewController inherits from ICETutorialController
#interface Tutorial2ViewController : ICETutorialController
ICETutorialPages have buttons that will trigger a callback. It takes in a block. So in my implementation, I have this:
- (id)init
{
ICETutorialPage *layer1 = [[ICETutorialPage alloc] initWithSubTitle:#"Page 1" description:#"Page 1" pictureName:#"Tutorial1_640x1136.png"];
NSArray *tutorialLayers = #[layer1];
self = [super initWithNibName:#"ICETutorialController_iPhone" bundle:nil andPages:tutorialLayers];
__weak Tutorial2ViewController *vc = self;
[self setButton1Block:^(UIButton *button){
NSLog(#"Button 1 pressed.");
[[vc.navigationController topViewController] dismissViewControllerAnimated:NO completion:nil];
}];
if (self != nil)
{
}
return self;
}
The reason why I put all the code in init is that I don't want SettingsViewController to know anything about how the Tutorial2ViewController works. Settings should alloc and init, push to the navigation controller stack and the Tutorial2ViewController should know how to handle itself.
I do get the NSLog that button1 is pressed but the view controller does not dismiss itself and return me to the SettingsViewController.
I will contact the creator of the library and ask him/her to see this question also. I feel that this is me not misunderstanding blocks, navigation controllers, cocoapods, etc...
Thanks
Just try [self.navigationController popViewControllerAnimated:YES];
I have 2 controllers InitViewController and SettingsViewController. Each view has a button that calls another view:
InitViewController
#interface InitViewController : UIViewController
- (IBAction)loadSettings:(id)sender;
#end
#implementation InitViewController
- (IBAction)loadSettings:(id)sender {
SettingsViewController *vc = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[self presentViewController:vc animated:YES completion:nil];
vc = nil;
}
#end
SettingsViewController
#interface SettingsViewController : UIViewController
- (IBAction)back:(id)sender;
#end
#implementation SettingsViewController
- (IBAction)back:(id)sender {
InitViewController *vc = [[InitViewController alloc] initWithNibName:#"InitViewController" bundle:nil];
[self presentViewController:vc animated:YES completion:nil];
vc = nil;
}
#end
While I was profiling application and testing it by tapping buttons many times I saw that instances of InitViewController and SettingsViewController are still living =>
What am I doing wrong?
Your SettingsViewController doesn't return to the InitViewController that created it. Instead, it creates a new instance of InitViewController and presents that. So you end up with a stack of view controllers alternating between instances of InitViewController and SettingsViewController.
Since you never dismiss either type of view controller after presenting it, none of them can be deallocated.
Your -[SettingsViewController back:] action should dismiss itself rather than creating and presenting a new InitViewController.
#implementation SettingsViewController
- (IBAction)back:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
I have a navigation based application.On click of a button on the navigation bar in the first screen , I am able to push another view controller as follows :
-(void) buttonClicked:(id)sender
{
UIViewController* mv = [[SecondViewController alloc] init];
[[self navigationController] pushViewController:mv animated:YES];
}
Now i have a UIView(separate .h and .m files) as part of the first screen. On click of a button in the UIView, i want to push the SecondViewController.
I have tried the following :
UIViewController* mv = [[SecondViewController alloc] init];
UIViewController * home=[[FirstViewController alloc]init];
[[home navigationController] pushViewController:mv animated:YES];
It doesnt work!! Kindly help
UIViewController* mv = [[SecondViewController alloc] init];
UIViewController * home=[[FirstViewController alloc]init];
[[home navigationController] pushViewController:mv animated:YES];
The problem here is that home isn't part of the navigation stack, so [home navigationController] is surely nil. I'm not quite clear on what you're trying to do here, but just creating a view controller doesn't mean that it's actually part of the view controller graph.
Why would it work? Randomly creating view controllers whose view is not even visible, is not the solution. You can either keep a reference to the VC in the view like this:
#imlementation ViewController
- (id) init
{
// ...
aView = [[CustomView alloc] init];
aView.viewController = self;
// ...
}
#end
#interface CustomView
#property (assign) ViewController *viewController;
#end
Or you can search the responder chain at runtime:
UIResponder *next = [view nextResponder];
while (next)
{
if ([next isKindOfClass:[ViewController class]])
{
break;
}
next = [next nextResponder];
}
And now "next" will contain the view controller (or nil if it can't be found).
Try using the same navigationController to push view, this keeps the same stack of ViewControllers.
UIViewController* mv = [[SecondViewController alloc] init];
[[self navigationController] pushViewController:mv animated:YES];
[mv release];
I see your problem now! You need to #import your FirstViewController, then #class it. Then do your push.
So:
//.h
#import "FirstViewContoller.h"
#class FirstViewController;
#interface...
//.m
-(void)return {
FirstViewController *firstview = [[FirstViewController alloc]init(withnibname:)];
[firstView.navigationController pushViewController: firstView.navigationController.topViewController animated: TRUE];
}
If I am not wrong, your UIView though is in separate files, is still added to the screen from a UIViewController class.
Simply, post a notification from UIView to your FirstViewController class where you have access to the navigation controller. Then push the SecondViewController from there.
You Can use this. It Works very well for me:-
Firstly Create Object of AppDelegate in UIView Class and initialize it. Then create Navigationcontroller object in Appdelegate.h :-
#property(strong,nonatomic) UINavigationController *navControl;
In your UIView Class implement this code where you want to push :-
ViewController *objview = [[ViewController alloc]init]; [appDelegate.navControl pushViewController:objview animated:YES];