iPhone - Multiple UIViewControllers Release - iphone

My main UIViewController, (PMGameViewController.h), is the file which my apps delegate calls.
There are several buttons on my main UIViewController (PMGameViewController.m). When a button is pressed I do an insertSuvbiew and attach another UIViewController on top. When the mini game is over I simply do a removeFromSubview. This removes the UIViewController I inserted on top and shows me the main menu. Perfect this is what I want, but...
After I do a removeFromSubview, the objectalloc does not drop. How can I release that UIViewController's memory. I do not know of a way to backreference to my main UIViewController (PMGameViewController.m) to tell it that it has been removed and to release the UIViewController memory.
Here is how I insert the Subview
//////////////////////////////////////
//Buttons are in PMGameViewController.m file
//////////////////////////////////////
if((UIButton *) sender == gameClassicBtn) {
//////////////////////////////////////
//This Inserts the GameClassic.h file
//////////////////////////////////////
GameClassic *gameClassicController = [[GameClassic alloc]
initWithNibName:#"GameClassic" bundle:nil];
self.gameClassic = gameClassicController;
[gameClassicController release];
[self.view insertSubview:gameClassicController.view atIndex:1];
}
if((UIButton *) sender == gameArcadeBtn) {
//////////////////////////////////////
//This Inserts the GameArcade.h file
//////////////////////////////////////
GameArcade *gameArcadeController = [[GameArcade alloc]
initWithNibName:#"GameArcade" bundle:nil];
self.gameArcade = gameArcadeController;
[gameArcadeController release];
[self.view insertSubview:gameArcadeController.view atIndex:1];
}

I'm don't know why you want to do this, since you might need your PGGameViewController afterwards. But if you really want to release it, you could do this :
PMGameViewController *tmpViewController = [[[UIApplication sharedApplication] delegate] viewController(or however it's called)]
to backreference it, then do your stuff and release it when you don't need it :
[tmpViewController release]
If you have to keep the reference for a while, you could create an id ivar in your two games view controllers, and use an asign protocol, but don't forget to set it to nil after releasing the controller :
id tmpViewController;
...
#property (nonatomic, assign) id tmpViewController;
...
#synthesize tmpViewController;

You can set the view controller to nil after you remove it . Prior to setting it to nil you can optionally release it. Whether you release it or not depends on use and how expensive it is to load.

Related

ViewDidLoad executes slowly while pushing viewcontroller

I was trying to push a viewcontroller B into navigation controller from A and then assigning some properties of B in A.
In this case, the assigning of properties was done and then viewDidLoad of viewcontroller A was executed.
Here, assigning properties in A should be done only after viewDidLoad of A has done.
For example,
[b.navController pushViewController:a animated:YES];
a.status = #"loaded";
Here, status was assigned first and then viewDidLoad of A was executed.
This happens only in iOS 7 whereas in iOS6 it works fine.
Can anyone please let me know where the problem is?
UPDATE: For me in some cases in iOS7, Push view is not working. How cna I debug and fix it?
Just access the viewcontroller.view (set any thing immediately after the alloc) property after the alloc init;
Which will loadview/viewdidload.
See Apple Documentation
In my experience, a UIViewController view is loaded lazily, no matter which iOS version you're working on. If you want to trigger a view load, and therefore its UIViewController viewDidLoad, you should access the view after the UIViewController is allocated. For example:
UIViewController *aViewController = [[CustomViewController alloc] init];
[aViewController view];
Make sure you don't code it as
aViewController.view
since that would generate a compiler warning.
So, in your case it would have to be something like this:
...
CustomViewController *a = [[CustomViewController alloc] init];
[b.navController pushViewController:a animated:YES];
[a view];
a.status = #"loaded";
Let me know if you have further problems with it.
You can know when a View Controller has been pushed onto the stack by implementing the UINavigationControllerDelegate and setting yourself as the delegate self.navigationController.delegate = self; then you will get this callback after every push
navigationController:didShowViewController:animated:
So you can check if the shown viewController is the one your interested in, then set your a.status.
I would suggest you call a delegate method once the view is loaded.
Set the delegate to be controller B.
and after viewDidLoad finishes (in controller A) call the delegate method. You can even pass parameters as you wish to the delegate.
Here's some example code:
Controller B:
a.delegate = self;
[b.navigationController pushViewController:a animated:YES];
Implement the delegate method:
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status
{
a.status = status;
}
Controller A .h file:
#class ControllerA;
#protocol ControllerADelegate <NSObject>
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status;
#end
#interface ControllerA : UIViewController
#property (nonatomic, weak) id <ControllerADelegate> delegate;
Controller A .m file:
- (void)viewDidLoad:(BOOL)animated
{
[super viewDidLoad:animated];
/* your viewDidLoad code here... */
if ([_delegate respondsToSelector:#selector(controllerIsLoaded:status)])
[_delegate controllerIsLoaded:self status:#"Loaded"];
}
Turn off animation for ios7, in my case its work
[b.navController pushViewController:a animated:NO];
a.status = #"loaded";
No documentation provides enough information to know exactly when viewDidLoad would be called.
UIViewController's documentation just says this
This method is called after the view controller has loaded its view hierarchy into memory
I would suggest that you create a custom initializer like this
- (id)initWithStatus:(NSString *)status {
}
But, if you are trying to use this variable to check if the viewController's view has 'loaded' or not, it may not be possible to do that because the pushViewController or presentViewController methods are not guaranteed to be synchronous.
Even in iOS 6, there was no explicit guarantee that the view would be loaded as soon as that method returned.
Please write the code in viewWillAppear method instead of viewDidLoad in next class i.e. where you are pushing the object to
-(void)viewWillAppear:(BOOL)animated
{
}
I'm understand of your question like this:
B *b = [[B alloc] init];
b.status = #"loaded";
[self.navigationController pushViewController:b animated:Yes];
If you want to pass a value from one controller to another means, you have to assign a value before using pushViewController method.

ScrollView won't scroll after adding as subview

So i'm adding UIView with UIScrollView in it to another view by pressing segment tool:
UIView.h
#interface gettingELORolesViewController : UIViewController{
UIViewController *currentController;
NSMutableArray *viewControllers;
}
- (IBAction)SegmentToggle:(UISegmentedControl *)sender;
#property (strong, nonatomic) IBOutlet UIView *containerView;
UIView.m
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Storyboard_iPhone"
bundle:nil];
firstView *FirstView = [sb instantiateViewControllerWithIdentifier:#"firstOne"];
secondView *SecondView = [sb instantiateViewControllerWithIdentifier:#"secondOne"];
viewControllers = [NSMutableArray arrayWithObjects:FirstView,SecondView,nil];
currentController = FirstView;
[containerView addSubview:FirstView.view];//containerView = self.view
and then depends on what segment is chosen, it shows different view:
- (IBAction)SegmentToggle:(UISegmentedControl *)sender {
UIViewController *selectedView=nil;
if (sender.selectedSegmentIndex==0) {
selectedView= [viewControllers objectAtIndex:0]; // retrieve object at index 0 from viewControllers array
}
else if(sender.selectedSegmentIndex==1){
selectedView= [viewControllers objectAtIndex:1]; // retrieve object at index 1 from viewControllers array
}
[currentController.view removeFromSuperview]; // remove Current displaying view from superView
currentController=selectedView; // make selected View to be the current View
[containerView addSubview:selectedView.view]; // Add newly selected view on container View
}
secondView is tableViewController and everything is working great when i choose second segment. When i choose first one it shows up but scroll doesn't work and i don't know why.
firstView.m viewDidLoad method looks like this:
-(void)viewDidLoad{
[super viewDidLoad];
mainScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
[mainScrollView setContentSize:CGSizeMake(320, 600)];
[mainScrollView setScrollEnabled:YES];
}
Paging is enabled in storyBoard. And it hooked up with .h file of firstView.
Please tell me what am i doing wrong.
When you call -SegmentToggle:,
you create a local variable called selectedView
you assign a view controller to selectedView depending on what segment is selected
the method ends, selectedView goes out of scope
In short, you're not doing anything. You should, at least, replace a view in the current view hierarchy with the view of the selected controller (and maybe stash a reference to the selected controller in an instance variable for later lookup?)
But taking a step back, it looks like you're trying to create a view controller that manages other view controllers. There are now very specific rules around that as of iOS 5. Please watch the WWDC 2011 video called "Implementing UIViewController Containment" for more information and examples.

Cocoa-Touch: Check if UIViewController exists before init it

Is there a way to check if a UIViewController is loaded in memory/visible on screen?
Something like this:
if([ContentRvController exists]){
contentView *ContentRvController = [[contentView alloc]
initWithNibName:#"contentView" bundle:nil]; //ContentView is a custom UIViewController
....
//Code to set the UIViewController
....
}
else{
[ContentRvController release];
}
That should happen when a button (that right now initializes the ViewControllers) is tapped. Right now, when tapped it opens n ViewControllers, it is supposed to display only one at a time.
Thats pretty much it, greetings and hope you can help me out.
Is this based on existing code?
Classes should start upper case, and instances should be camel case, e.g.
if([contentRvController exists]){
ContentView *contentRvController = [[ContentView alloc]
initWithNibName:#"contentView" bundle:nil]; //ContentView is a custom UIViewController
....
//Code to set the UIViewController
....
}
else{
[contentRvController release];
}
it is probably worth declaring it in the header, i.e.
#interface SomeClass : NSObject {
}
#property(non-atomic, retain) ContentView *contentRvController;
#end
and then in code you could do
if(contentRvController!=nil){
ContentView *aView=[[[ContentView alloc] init] autorelease];
self.contentRvController=aView;
}
Also, don't do the else{[contentRv release];} bit, if you have autoreleased it anywhere, this will leak at some point.
If you are using UINavigationController check for the property topViewController.

Allocating and releasing of UI controllers

I am new to iOS programming and I have read some useful articles on the release and allocation of memory and thought that i understand the concept. But during the actual coding, I can't seem to really apply the theory. I am really confuse over the fundamentals of iOS programming and hope that someone can help out. Thanks in advance!
1st behaviour - Some of the apps i see can maintain the current windows state when the iPhone home button is pressed so that when the app is launch next time, it will display the last state it is in.
2nd behaviour - Some other apps will behave like its a new startup everytime it is launch. Previously displayed text, images...etc will be cleared and it will always start at the first page.
What I wanted to do is like the 2nd behaviour - clear everything when the home button is pressed and start afresh everytime it is launch.
I have created a tab bar project. The codes below will result in the 1st behaviour.
I have tried releasing all the tab controllers at applicationDidEnterBackground instead at dealloc but it didn't work. It will still display the last screen.
My questions...
1) If i call release, shouldn't it destroy the window as well? eg. [NavController1 release]. It seems that the window can still work as usual...
2) What should I change to result in the 2nd behaviour?
3) If i assign tabBarController.viewControllers = nil, does it mean that the memory for the pages attached to it previously will be release?
4) Do i need to release IBOutlets variables? eg. UIWindow *window, UITabBarController, UITextField...etc. It didn't seem to give me memory leak even if i don't release them. The reason of this might be because I don't know how to kill the app. I tried to do the double click on the iphone home button to end the app but i still not able to get the debug to reach the dealloc function. An article says that if you alloc or retain it, you should release it. Since i'm not allocating anything here, can i not release it? Or is it possible to set autorelease?
#interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
UINavigationController *NavController1;
UINavigationController *NavController2;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NavController1 = [[UINavigationController alloc] init];
NavController2 = [[UINavigationController alloc] init];
//This view controller inherits UIViewController and has a xib
FirstViewController *firstpage = [[FirstViewController alloc] init];
firstpage.title = #"First Page";
//These view controllers inherits UIViewController and delegate of UINavigationControllerDelegate and has a xib
SecondViewController *secondpage = [[SecondViewController alloc] init];
secondpage.title = #"Second Page";
[NavController1 pushViewController:secondpage animated:YES];
[secondpage release];
ThirdViewController *thirdpage = [[ThirdViewController alloc] init];
thirdpage.title = #"Third Page";
[NavController2 pushViewController:thirdpage animated:YES];
[thirdpage release];
// Add them as children of the tab bar controller
tabBarController.viewControllers = [NSArray arrayWithObjects:firstpage, NavController1, NavController2, nil];
// Don't forget memory management
[firstpage release];
// Add the tab bar controller's view to the window and display.
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
/*
Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background.
*/
}
- (void)dealloc {
[NavController1 release];
[NavController2 release];
[tabBarController release];
[window release];
[super dealloc];
}
1) As long as the retain count is 1 your view controller will be released by calling release.
In your code, dealloc will never be called, and therefore your view controllers will not be released anyway. You need to release everything in applicationDidEnterBackground...
2) in applicationDidEnterBackground do something like:
[NavController1 release];
NavController1 = nil;
[NavController2 release];
NavController2 = nil;
tabBarController.viewControllers = nil;
then recreate everything again in
(void)applicationWillEnterForeground:(UIApplication *)application
3) doing this will release firstpage, NavController1 and NavController2 (and any other view controllers you may have added)
4) yes you should. You do not get a memory leak in this instance because the delegate never gets dealloc'd and therefore there is still a valid reference to the objects. No you cannot autorelease as autorelease will release the object when the call stack returns to the run loop.

UIViewController memory management

Hi I have a very basic issue of memory management with my UIViewController (or any other object that I create);
The problem is that in Instruments my Object allocation graph is always rising even though I am calling release on then assigning them nil.
I have 2 UIViewController sub-classes each initializing with a NIB;
I add the first ViewController to the main window like [window addSubView:first.view];
Then in my first ViewController nib file I have a Button which loads the second ViewController like :
-(IBAction)loadSecondView{
if(second!=nil){ //second is set as an iVar and #property (nonatomic, retain)ViewController2* second;
[second release];
second=nil;
}
second=[[ViewController2 alloc]initWithNibName:#"ViewController2" bundle:nil];
[self.view addSubView:second.view];
}
In my (second) ViewController2 i have a button with an action method
-(IBAction) removeSecond{
[self.view removeFromSuperView];
}
Please let me know if the above scheme works in a managed way for memory...?
In Instruments It does not show release of any allocation and keeps the bar status graph keeps on rising.
First of all, why use this scheme when second is a property:
if(second!=nil){
[second release];
second=nil;
}
second=[[ViewController2* second]initWithNibName:#"ViewController2" bundle:nil];
A propery automatically releases it's old value when the setter is used. So this could be rewritten as:
if(self.second == nil) { //Prevents creating a new controller if there already is one.
self.second = [[[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:nil] autorelease];
}
Also, what's up with [ViewController2* second]?
Why is that asterisk there, and what does the class method second do?