View controllers not being deallocated - ios

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];
}

Related

How to dismiss 2 view controller

I have an issue need to support:
Assume, I have the controller A and View Controller B. The view controller A present view controller B. After that view controller B load content completed. At View controller B I have a button.
How to dismiss both view controller when I touch event button touch up inside?
Here my code: At view controller A
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"mainstoryboard" bundle:nil];
ViewControllerB *viewcontrollerB = [storyBoard instantiateViewControllerWithIdentifier:#"viewcontrollerB"];
[self presentViewController:viewcontrollerB animated:YES completion:nil];
And code in view controller B
- (IBAction)buttonTouchUpInside:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
Thanks for helping me. You will save my time. :)
Use delegation. When your button pressed call delegate, and in delegate method implementation write following:
[self dismissViewControllerAnimated:NO completion:nil];
Use this delegation on your ViewControllerB
On ViewControllerB.h do this
#protocol ViewControllerDismissDelegate;
#interface ViewControllerB : UIViewController
#property(nonatomic) id<ViewControllerDismissDelegate> delegate;
#end
#protocol ViewControllerDismissDelegate <NSObject>
#optional
- (void)didViewControllerBDismiss;
#end
On ViewControllerB.m do this
// Button action
- (void)buttonDidClick {
if ([self.delegate respondsToSelector:#selector(didViewControllerBDismiss)]) {
[self dismissViewControllerAnimated:NO completion:nil];
[self.delegate didViewControllerBDismiss];
}
}
Learn more about delegates and data sources here
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html

nil delegate in modal view after presentViewController.

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

presentViewController didnt work appdelegate

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];

UIViewController not being released from memory when popped

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

Property nil when using delegate to pass data back to VC

I've got a view in a xib file that I load to my viewController - its a temp view and thus isn't on screen all the time. However the temp view and the main view, use the same view controller class as they both are doing the same job.
Now, in the xib view I load up a second viewControoler - lets call it viewControllerB - which is a UITableView with 197 cells. Once a cell is selected, the view is dismissed and the value of the cell is returned to viewControllerA.
viewControllerB is on the storyboard.
Here is the code I have used:
viewControllerB (The VC I need data back from)
Delegate Protocol:
#protocol CountrySelectedDelegate <NSObject>
-(void)selectedCountry: (id)countryReturned;
#end
#property (strong, nonatomic) id <CountrySelectedDelegate> myDelegate;
Then in the .m file:
Set the delegate:
ViewControllerA *viewA = [[ViewControllerA alloc]init];
self.myDelegate = viewA;
Then I call the delegate in the same file;
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[[self myDelegate] selectedCountry:indexPath.row];
[self dismissViewControllerAnimated:YES completion:nil];
}
Now in viewControllerA:
pragma mark - CountrySelected Delegate
-(void)selectedCountry:(id)countryReturned
{
self.testInt = countryReturned;
}
Now this method does get called by the delegate. Looking in the console: countryReturned is the correct value. Once this method has finished, self.testInt is also the correct value.
Then, in my viewWillAppear method, I simply check the value again to see if its still the same with a NSLog print.
the value is always nil in the viewWillAppear method (Or anywhere else I check it) - I have tried with NSMutableString and NSString - always nil.
Why are my properties / instance variables getting set to nil?
Update:
This is how I show viewControllerB
- (IBAction)countrySelect:(UIButton *)sender
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
UITableViewController *vc = [sb instantiateViewControllerWithIdentifier:#"CountrySelection"];
vc.modalTransitionStyle = UINavigationControllerOperationPop;
[self presentViewController:vc animated:YES completion:NULL];
}
On ViewB do the below I guess you have already done
#protocol CountrySelectedDelegate <NSObject>
-(void)selectedCountry: (NSInteger)countryReturned;
#end
#property (week, nonatomic) id <CountrySelectedDelegate> myDelegate;
and in didSelectRowAtIndexPath
if ([[self myDelegate] respondsToSelector:#selector(selectedCountry:)])
{
[[self myDelegate] selectedCountry:indexPath.row];
}
On ViewA
Conform the protocol
#interface ViewA : UIViewController < CountrySelectedDelegate >
create the call ViewB
ViewB *viewBObjc=[[ViewB alloc] init];
[viewBObjc setMyDelegate:self];
[[self navigationController] presentViewController:ViewB animated:YES completion:Nil];
implement the delegate on ViewA
-(void)selectedCountry: (NSInteger)countryReturned
{
NSLog(#"Country Id is :%d", countryReturned);
}
Try this,
Set delegate by
//in ViewControllerB
self.myDelegate = [self parentViewController];
or
- (IBAction)countrySelect:(UIButton *)sender
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
ViewControllerB *vc = (ViewControllerB *)[sb instantiateViewControllerWithIdentifier:#"CountrySelection"];
vc.modalTransitionStyle = UINavigationControllerOperationPop;
vc. myDelegate = self;
[self presentViewController:vc animated:YES completion:NULL];
}

Resources