How do I remove a no longer needed view controller? - ios

I am using my app delegate to transition between view controllers. When the delegate decides it no longer needs the view controller, based on messages from the server, it needs to remove the current view and replace it with another one. Currently my code looks like the following:
- (void) showFight: (NSNotification*) notification
{
if(self.window.rootViewController != self.fightViewController)
{
NSDictionary* dict = [notification userInfo];
FightViewController *fightView = [[FightViewController alloc]
initWithNibName:#"FightViewController" bundle:nil];
fightView.userId = _userId;
self.fightViewController = fightView;
[fightView release];
[self.radarViewController.view removeFromSuperview]; // Doesn't work.
self.window.rootViewController = self.fightViewController;
[self.fightViewController showMonster:dict];
}
}
I know my view controller isn't being removed because I can hear sound effects from it in the background.
I want to completely destroy the view controller, as I only want one view controller in memory at any time. I plan to create the view controller each time from scratch, as shown in the code above. Am I doing this improperly?

The problem here seems to be that you are not releasing the view controller. Think about what actually happens in your code at:
[self.radarViewController.view removeFromSuperview];
You remove the view from its super view. The view controller still exists, and what it does is control what should be shown on the view, and in your case apparently playing sound.
Put in an easy way: The view controller is an object. It has a child, the view. That's another object. You remove the view from another view, but the object controlling the removed view still lives (and actually, so does the view object).
If you want to kill the entire view controller, call this after removing the view from its superview:
[self.radarViewController release];
Or, if the view is a retain property (which i assume by looking at your code) you can also use:
self.radarViewContoller = nil;
which automatically releases for you in the synthesized setter.
When the view controller is released, its reference count is subtracted by one. If the reference count reaches zero, then the controller will be deallocated.

So far I understand your problem is to add the new ViewController on server notify and change the current view with new View. First of all you've to add the view controller just like below because the reference won't help to update the view.
[self.window.rootViewController.view addSubview: self.fightViewController.view]
In my opinion you need to tag your Controllers and check before adding the controller that if it's already exist in the memory, otherwise the pool of object will leak. Just Say No to Memory Leaks!!
- (void) showFight: (NSNotification*) notification
{
UIView *fightView = (UIView *)[self.window.rootViewController.view viewWithTag: FIGHT_VIEW_TAG];
if (self.window.rootViewController.view.tag != fightView.tag) {
NSDictionary* dict = [notification userInfo];
FightViewController *fightView = [[FightViewController alloc]
initWithNibName:#"FightViewController" bundle:[NSBundle mainBundle]];
//Remove the current view.
[self.window.rootViewController.view removeFromSuperview]; // If you're adding the fighting View in the root View, then why are you trying to remove current view through radar controller which has already added in the window (root view).
fightView.userId = _userId;
[fightView setTag: FIGHT_VIEW_TAG];
[self.window.rootViewController.view addSubView: self.fightViewController.view];
[self.fightViewController showMonster:dict];
[fightView release];
}
}
You don't need to take them as global until your requirements are different.

Related

Custom object of same type added multiple times inside for loop

I am allocating custom object (viewcontroller in this case) inside for- loop. And everything seems to work fine. But when I tap on the button of first custom object of viewcontroller, the application crashes.
It is because the instance for the custom object is not retained. Although it works fine for the last added object.
Please advice.
dispatch_async(dispatch_get_main_queue(), ^{
NSInteger index = 0;
for (TestStep *obj_Teststep in objTestSuite.testSteps) {
TestStepView * obj_TestStepView = [[TestStepView alloc] initWithNibName:#"TestStepView" bundle:[NSBundle mainBundle]];
obj_TestStepView.testStep = obj_Teststep;
obj_TestStepView.delegate = self;
DMPaletteSectionView *sectionView = [[DMPaletteSectionView alloc] initWithContentView:obj_TestStepView.view andTitle:[NSString stringWithFormat:#"Test Step %# - %#",obj_Teststep.executionOrder,obj_Teststep.apiCallPath] initialState:DMPaletteStateCollapsed withAction:YES andIndex:index];
sectionView.layer.backgroundColor = [NSColor redColor].CGColor;
[sectionArray addObject:sectionView];
index++;
}
[sectionArray addObject:[[DMPaletteSectionView alloc] initWithContentView:self.addNewTestStepView andTitle:#"Add Test Step" initialState:DMPaletteStateExpanded withAction:NO andIndex:0]];
container.sectionViews = sectionArray;
for (int i =0; i<container.sectionViews.count; i++) {
DMPaletteSectionView *dmobj = [container.sectionViews objectAtIndex:i];
dmobj.delegate = self;
}
});
As #trojanfoe says, your design is faulty. You can't create a view controller and add it's view to another view controller without maintaining a strong reference to the view controller.
You create a bunch of TestStepView objects (which I assume are view CONTROLLERS?) Then you pass the view of those objects to a DMPaletteSectionView, but never retain a strong reference to the TestStepView object. That won't work.
When you add a view controller's view to another view controller you should use the parent/child view controller support that was added to iOS (in iOS 5, if I remember correctly.) Do a search in the Xcode docs in the UIViewController class reference for the words "parent" and "child". There are a family of methods that let you set that up.
You would need to make your TestStepView (view controller?) a child of the DMPaletteSectionView (view controller?)
BTW, stop calling view controllers views, both in your question and in your code. View objects and view controller objects are totally different, and you will confuse both yourself and your readers by calling view controllers views.
I use the abbreviation VC for view controllers in my code to keep my class names shorter, while keeping it clear that they are view controllers, not views.
You are allocating the view controllers and then effectively throwing them away as ARC will deallocate them when they go out-of-scope:
for (TestStep *obj_Teststep in objTestSuite.testSteps) {
TestStepView * obj_TestStepView = [[TestStepView alloc] initWithNibName:#"TestStepView"
bundle:[NSBundle mainBundle]];
// ...
// ARC will deallocate obj_TestStepView here
}
This isn't how you are supposed to use view controllers; they are expected to be presented (normally one-at-a-time), so what you are doing is undefined.

uiview duplicated when switching view controller

I have 2 ViewControllers that I use App delegate to switch them according to user interaction.
in AppDelegate.m I have:
- (void) switchViews
{
if (_viewController.view.superview == nil) {
[_window addSubview:_viewController.view];
[_window bringSubviewToFront:_viewController.view];
[viewController2.view removeFromSuperview];
} else
{
[_window addSubview:_viewController2.view];
[_window bringSubviewToFront:_viewController2.view];
[_viewController.view removeFromSuperview];
}
}
_viewController is for main view and _viewController2 is for glview(I am using isgl3d). The switch works but everytime I switch back to glview, I see duplicated view on top, which I suspect even main view is duplicated too.
Any idea how can I remove the view entirely so that I don't have this issue? Thanks!
You shouldn't be adding and removing the views like this, just change which controller is the root view controller of the window. Doing that make the new controller's view a subview of the window, and removes the old controller's view.
if ([self.window.rootViewController isEqual: _viewController]) {
self.window.rootViewController = viewController2;
}else{
self.window.rootViewController = viewController;
I found out how to do this after watching Stanford Coding Together:IOS.
Some critical info of VC that I am not aware of:
Everytime VC is instantiate, viewDidLoad is called once to setup all the important stuff like outlets and such. Then viewWillAppear and viewWillDisappear will be called for in between view swapping. Because it is called just a moment before view is shown to user, all the geometry setting like view orientation and size is set here.
so what I do is:
I addSubview in viewDidLoad, the do all the running setup in viewWillappear and viewWillDisappear.
one more note: view will remain there as long as the app still running.
anyway Thanks rdelmar for helping.

addSubview notification not received

I'm facing a strange problem..
I want to add a UIViewcontroller (called iView) to my current View.
I do it by calling
iView = [[KFKaraokeInfosView alloc] initWithKaraoke:karaoke NibName:#"InfosView" commingFromPlayer:NO];
iView.songTitle.text = karaoke.title;
[self.view addSubview:iView.view];
In the viewDidLoad of iView, I add it as an observer to the NotificationCenter for a certain notification, like this
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"About";
if ([karaoke.styles count] == 0)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"GetInfosOfSong" object:self.karaoke];
}
else
{
shouldSetup = YES;
}
[self.navigationController setNavigationBarHidden:NO animated:YES];
[self.navigationController.navigationBar setBarStyle:UIBarStyleBlack];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(setup) name:GetSongInfos object:nil];
[optionsTableView setBackgroundView:nil];
}
The problem is when I call autorelease method on iView at the initialisation, the notification is never catched (so setup method is never called), but if I don't call autorelease for iView, it works.
I don't understand the memory management in this situation, can someone help me to understand ?
The container view controller methods are found in Implementing a Container View Controller of the UIViewController Class Reference, and sample code can be found in Creating Custom Container View Controllers of the View Controller Programming Guide.
Thus, in iOS 5 and later, you should probably have:
iView = [[[KFKaraokeInfosView alloc] initWithKaraoke:karaoke NibName:#"InfosView" commingFromPlayer:NO] autorelease];
[self addChildViewController:iView];
iView.songTitle.text = karaoke.title;
[self.view addSubview:iView.view];
[iView didMoveToParentViewController:self];
If this is iOS 4 or earlier, it doesn't support proper containment. You can manually kludge it, by adding the view like you are, with no autorelease:
iView = [[KFKaraokeInfosView alloc] initWithKaraoke:karaoke NibName:#"InfosView" commingFromPlayer:NO];
iView.songTitle.text = karaoke.title;
[self.view addSubview:iView.view];
You'd obviously keep a copy of the child view controller in some ivar of the parent view controller, not autorelease it, but rather explicitly release the child's controller when the child's view is dismissed. Note, since you're operating in a pre-containment iOS4 world, your child controller is not guaranteed of receiving various events (notoriously rotation events). But you should receive your notification center events.
An alternative to this ugliness of trying to fake containment in iOS 4 is to not use a child view controller at all, but just add the child view to the parent view. You can actually add it to the parent controller's NIB, but just hide it. Then, when you want to present it, unhide it. But keep everything in the parent view controller and it's NIB. The method that I described above, faking containment, might work (I've seen people do it), but it gives me the heebie jeebies. This is simpler.

Adding another view controller's view as subview

I am trying to get a popup effect and want to design the popup view in another view controller so i can use the xib to do it.
When i used the presentViewController or pushViewController and set the background to transparent, i end up seeing the Window's background color.
I tried this code to add subview to the navigation controller's view so that i can have the Info view cover the entire screen with a transparent background. I also have tab bar to cover up as well.
InfoVC *vc = [[InfoVC alloc] initWithNibName:#"InfoVC" bundle:nil];
[self.navigationController.view addSubview:vc.view];
My problem is inside my InfoVC when i try to dismiss it, the app will crash with some EXC_BAD_ACCESS message:
[self.view removeFromSuperview];
EDIT:
I found a way to stop it crashing but setting the InfoVC as a property in the MainVC. I think the reason for crash is when i call "self.view" in the action inside the InfoVC, it doesn't know that self is the InfoVC inside MainVC.
InfoVC *vc = [[InfoVC alloc] initWithNibName:#"InfoVC" bundle:nil];
[self.navigationController.view addSubview:vc.view];
No no no no. Never never do that.
There is an elaborate dance that you must traverse in order to put a view controller's view inside another view controller's view (or remove it afterwards) if it doesn't come with built-in facilities for doing this (the way a UISplitViewController does, or the way a navigation controller manages the views of the view controllers that are pushed and popped within it).
Read up on customer container controllers. One of the examples from my book is here:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch19p556containerController/p476containerController/ViewController.m
Shouldn't you be using the following to remove the view from its superview?
[vc.view removeFromSuperview];
You can never have a UIView remove it's subviews, the subviews themselves must remove themselves from it's superview. You can easily loop through subviews and have them removed like so
for (UIView *view in vc.view.subviews) {
[view removeFromSuperview];
}
Docs for reference:
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/uiview/uiview.html
After a "modally" presented view controller has appeared the views under the now presented view controller will be removed; this saves memory, and eases rendering. In your case, though, you also end up seeing the window behind the "modally" presented view.
The natural, and seemingly logical, next step is to simply take one view controller's view and cram it into another. However, as you have discovered, this is problematic. With the newly inserted view safely retained by the view hierarchy it is safe, but the new view controller is not so lucky, it is quickly deallocated. So when this new view tries to contact its controller you will get an EXC_BAD_ACCESS and crash. One workaround, again as you have found, is to simply have the original view controller keep a strong reference to the new view controller. And this can work... badly. There's still a good chance you will get an UIViewControllerHierarchyInconsistencyException.
Of course if you simply want to add a small view you create in IB you don't need to use a view controller as the "File's Owner" and there are many examples of creating an instance of a view from a xib file.
The more interesting question here is, "How would/does apple do it?" Apple consistently says that a view controller is the correct controller for an encapsulated unit of work. For example, their TWTweetComposeViewController, you present it, and it seems to float. How?
The first way of accomplishing this that comes to my mind is to have a clear background that isn't clear. That is, create an image of the screen before the presented view controller appears and set that as the background before the presenting view is removed. So for example(Explanation to follow):
QuickSheetViewController.xib
QuickSheetViewController.h
#import <UIKit/UIKit.h>
#interface QuickSheetViewController : UIViewController
- (IBAction)dismissButtonPressed:(id)sender;
#end
QuickSheetViewController.m
#import "QuickSheetViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation QuickSheetViewController {
UIImage *_backgroundImage;
}
-(void)renderAndSaveBackgroundImageFromVC:(UIViewController *)vc{
UIGraphicsBeginImageContext(vc.view.bounds.size);
[vc.view.layer renderInContext:UIGraphicsGetCurrentContext()];
_backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// save an image of the current view, and set our background to clear so we can see the slide-in.
[self renderAndSaveBackgroundImageFromVC:self.presentingViewController];
self.view.backgroundColor = [UIColor clearColor];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Time to use our saved background image.
self.view.backgroundColor = [UIColor colorWithPatternImage:_backgroundImage];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// Set our background to clear so we can see the slide-out.
self.view.backgroundColor = [UIColor clearColor];
}
- (IBAction)dismissButtonPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The majority of this example hinges upon the renderAndSaveBackgroundImageFromVC: method. In which, we create a graphics context render the view we are about to cover into it, and then create a UIImage to later (in viewDidAppear) use as a background.
Now simply use it like:
QuickSheetViewController *newVC = [[QuickSheetViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController:newVC animated:YES completion:nil];
You will see through the background just long enough for the animation to happen, then we use our saved image to hide the removal of the presenting view.

Cannot pass messages between main view controller and popover view

I'm seem unable to get any kind of communication going between my Main View Controller and a Table View Controller which is being displayed inside a Popover View (iPad).
I'm setting up the Table View inside a Navigation Controller in the usual way:
// create popover
if (self.popoverController == nil) {
filesViewController = [[[MyTableViewController alloc] initWithFiles:fileList] autorelease];
UINavigationController *navCtrl = [[[UINavigationController alloc] initWithRootViewController:filesViewController] autorelease];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:navCtrl];
self.popoverController.delegate = self;
// resize popover
self.popoverController.popoverContentSize = CGSizeMake(320.0, 44 + [fileList count] * 44);
}
Everything is working fine, and I'm passing an array of file names (fileList) into the Table View, which is held in the Table View as an array called listOfFiles. The Table View displays the filenames, and when one is selected by the user I want to pass that filename back to the Main View Controller. However, I cannot get any communication going back from the Table View's didSelectRowAtIndexPath method to the Main VC. I've tried all sorts of outlets going in various directions, and I've tried creating a new object in didSelectRowAtIndexPath to handle the filename coming from the Table View. I can pass the filename out to the new object, but when I try to send that into the Main VC it is null again. Everything I send to my Main VC while that popover is active comes up as null.
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
NSLog(#"%#", handler.addressForImageFile);
self.popoverController = nil;
[self.popoverController release];
}
Is there some reason why my Main VC won't get anything but null objects from my Table View? I've spent days trying so many different things. I feel like there's some fundamental gap in my knowledge of how popovers work. Surely there is a simple way to send a string back to my Main VC when it is selected from the Table View?
Thanks so much for any help!
There's propably a much better way to do this, but depending on the goal of passing the string, one way could be to use NSUserDefaults.

Resources