Memory management, addSubview for a subclass of UIViewController - iphone

I have a view that shows a map. I have a custom subclass of UIViewController (DetailViewController) that gets shown when the detailDisclosureButton of the callout above the pin is pressed. While in my map class, I create my detailview and add it to the subview like this:
DetailViewController *detailView = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
detailView.locationPoint = locationPoint;
detailView.locationCoordinate = locationCoordinate;
[self.view addSubview:detailView.view];
[detailView release];
My DetailViewController has a TableView and parses the data in DetailViewController. However I get an error of sending the numberOfSectionsInTable message to a dealloc'd instance. I'm assuming it is this since I originally had this as a property and it worked fine with (nonatomic, retain). I'm assuming that I'm releasing it before the next view is done with it. If that is the case, when would I clean up the memory??? It seems like this would be the place to do it. Thanks.

I am not sure what makes you adding the view of DetailViewController into this mapviewcontroller's view. Don't you think right approach would be to either presentModalViewController or pushNavigationController?
DetailViewController *detailView = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
detailView.locationPoint = locationPoint;
detailView.locationCoordinate = locationCoordinate;
//[self.view addSubview:detailView.view];
[self.navigationController pushViewController:detailView animated:YES];
//OR
[self presentModalViewController:detailView animated:YES];
[detailView release];
You are getting the error because you are only using the view and deallocating the view controller immediately and hence tableview datasource and delegates are hitting a nil object.

Views do not retain their view controllers. Someone needs to retain the VC or else it will get released, and then the app will crash when the view makes a call into its delegate. When you use a navigation controller, the navcon has a stack of view controllers that it retains. Likewise with presentModalViewController, the OS takes care of retaining the detail VC.
Adding a detail view as a subview is not the normal way to navigate to a new view. Instead, one either uses a navigation controller and [navcon pushViewController::], or a modal subview and [self presentModalViewController::]. If the detail view occupies only a portion of the parent view, then it is normal to retain the view controller for the subview within the parent controller. That is, within the parent VC (your map class) add a property for the detail VC. Actually, it's more common to not even use a VC for a subview, but rather for screen-filling detail views.

Related

How to push DetailView without NavigationController on UIViewController

I have a ViewBased App. I added a UITableView on one of the UIViewControllers. It shows the data and I implemented all the delegate methods etc. My problem is when I want to show the detailView it just doesn't happen. My code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailViewController =[[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
NSLog(#"DidSelectRowAtIndexPath");
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
I see that I need a navigationController but I don't have one and I was unsucessful trying to add one programatically. I don't have one in my appDelegate either, so my question is do I need to add one to show the detail view? If yes, please give me a code sample how to do that.
If not, what other way is there?
I am new to iOS so I am a bit lost here.
Please help!
To add a navigation controller programmatically just for this detail view, you need to something like this:
UINavigationController * controller = [[UINavigationController alloc] initWithRootViewController:detailViewController];
[[detailViewController] release];
[self presentModalViewController: controller animated: YES];
If you want to use pushViewController, you need to already have a navigation controller surrounding the view you're starting with.
You need to add the Navigation Controller FIRST, then your master table becomes the root view controller of the nav controller, then when you tap a row in the table, you push another view controller onto the nav stack.
How does your master table get into the app in the first place? If you're using a nib, it's super easy to just change out the view controller for a nav controller with the old view controller added as a child of the nav controller.
You can create one programmatically by working within your app delegate's application:didFinishLaunchingWithOptions: method like so:
UITableViewController *tableViewController = [[[WhateverYourSubclassVCIsCalled alloc] init] autorelease];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tableViewController];
window.rootViewController = navController;
[window makeKeyAndVisible];

Set view controllers property before pushViewController

In my app I've added a label to a view, connected it to an outlet but nothing shows up when I first assign this outlet from another view controller and then call pushViewController to display it. Here's the code before pushing next view that display the label:
CustomViewController *vc = [[CustomViewController alloc] init];
vc.lbl_price.text = self.label_price.text; // lbl_price is defined as a property in CustomViewController and label_price is defined in current view controller
[self.navigationController pushViewController:vc];
In the CustomViewController viewDidLoad method I added this instruction to see if it should work
NSLog(#"Price=%#",lbl_price); // it actually prints out what was previously assigned
But it doesn't show into the label!
Any idea why ?
Stephane
Even if view controller is created its view hierarchy may not (and so all subviews will still be nil), for optimization reasons it may not be loaded until you try to actually access controller's view. You have two options to solve your problem:
Store all values in separate non-UI variables and assign them to UI components with controller is going to appear:
// Before push controller
vc.myPriceText = self.label_price.text;
// In controller's viewWillAppear:
self.lbl_price.text = self.myPriceText;
Make [vc view] call to force controller to load its view hierarchy:
CustomViewController *vc = [[CustomViewController alloc] init];
[vc view];
vc.lbl_price.text = self.label_price.text;
[self.navigationController pushViewController:vc];

pushViewController, when to set UILabel text, and does setNeedsDisplay need to be called?

I ran into something odd today that maybe someone knows something about. I have a subclass of UIViewController and its associated NIB. I set the labels in the UIViewController methods and all that works fine.
Now from another class, I create that ViewController again because I want to reuse it. I do this:
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
vc.titleLabel.text = #"testing";
vc.myTextLabel.text = #"yo";
self.navigationController pushViewController:vc animated:NO];
[vc release];
This does NOT work. I have no idea why this does not work. I would think I would set all the labels, then show the view controller by pushing it onto the stack.
However, if I do this:
[vc.view setNeedsDisplay]; // why here???
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
vc.titleLabel.text = #"testing";
vc.myTextLabel.text = #"yo";
self.navigationController pushViewController:vc animated:NO];
[vc release];
This DOES work. This does not make sense to me. I thought setNeeds Display was called AFTER a view needs to be redrawn. If I move setNeedsDisplay to the end of the block it does NOT work. It only works at the beginning of the block which is very odd to me. Any one encounter this before or know why it works this way? Thanks.
The reason is that a view controller's view is lazily-loaded. This means the controller's view is only loaded from a nib (or via -loadView) when you access the view property for the first time. If you attempt to access the labels before the view has been loaded, they will be nil and any messages you send to them will be no-ops.
So to force the view to load, you can do this:
/* make sure the view is loaded */
[vc view];
/* Access the label properties */
vc.titleLabel.text = #"testing";
However, forcing the view to load may not be a good idea in all situations, especially if the view controller is not going to be displayed immediately and you want to save memory.
In this case you can create the labels in the controller's init method so they always exist, and add them to the view controller's view manually in -viewDidLoad, rather than in your nib. This will allow the standard lazy-loading behaviour to work, but users of your class can still set properties on the labels before the view is loaded.
An alternative is to expose simple NSString properties with associated ivars on the view controller to represent any titles or text in the view. Then in your -viewDidLoad you can set the text of the labels to the value of these properties. Users of your view controller can then set these properties before the view has loaded.

A couple of problems customizing Apples MultipleDetailViews Example

I have been trying to add/implement this example to my existing Split View app tests.
Apple Example
I what to use the concept of replacing the detail view or right view, otherwise my app will be different. It is this difference that is causing my problems.
I have a rootviewcontroller or left view and upon choosing something here a new view is pushed onto this view. Upon choosing something in this "pushed view" I want to change the detail view or right hand view. This is the difference to apples example where the rootview does not have a pushed view on it and thus references are not broken.
Below is my change code - the new View DVCases is being initialized but the didload is not happening.
The issues are learner issues to do with my classes.
This below code is in my RootViewController implementation code but my reference to splitviewcontroller is not working if there is a new view pushed.
Second self.navigationcontroller is not correct because I have pushed a second view to the rootviewcontroller.
To centralize and simplify the code what I have done is from the delegate of the pushed view in the didselect event i call a method found in the rootviewcontroller passing the index as a parameter. The code for my custom method contains what is below.
So my question is how do I do this in my situation where I have pushed other views onto the rootview or left side. It appears that after pushing a view the reference to splitviewcontroller is gone and self.navigationcontroller is also gone/or wrong.
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (value == 0) {
DVCases *newDetailViewController = [[DVCases alloc] initWithNibName:#"DVCases" bundle:nil];
detailViewController = newDetailViewController;
}
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
[viewControllers release];
// Dismiss the popover if it's present.
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
// Configure the new view controller's popover button (after the view has been displayed and its toolbar/navigation bar has been created).
if (rootPopoverButtonItem != nil) {
[detailViewController showRootPopoverButtonItem:self.rootPopoverButtonItem];
}
[detailViewController release];
I would appreciate any tips or help you might have.
Initialization of any viewcontroller class does not mean that it will make call to viewDidLoad method.
viewDidLoad method will only be called when you load view of that viewController. Generally we do it either by following methods.
1. Pushing it on navigation stack.
2. Presenting it using modal transition.
3. Adding it on some other view using [someView addSubView:controller.view];
4. Selecting any tabBar item for the first time Or tapping tabBar Item twice.
there may be some other scenarios.
But right now in your code I don't see any of this element.
Initialization means you are calling the direct method for intialization(calling its constructor) like here in above code initWithNibName will call this method of DVClass not any other(until this method had call for other methods inside it).
Thanks
As I am learning to properly code - my problems centres around that.
The above code is perfect as long as you call it using the same instance. I was not. Thus it was not working.
In the end I made my RootViewController a delegate for a method that has the above code. Thus when in another view - this view can call this method and the proper or real instance of RootViewController will implement it.

iPhone: ViewController as a tab bar

Hey guys I need some help with this:
I have two view controllers, let's say:
FirstViewController (first) is inside a navigationviewcontroller
SecondViewController (second)
So in the first's viewDidLoad method I have this:
SecondViewController *second = [[SecondViewController alloc] initWithNibName:...];
[self.addsubview:second.view];
I've done this cuz I want to make my custom tab bar with my custom buttons and colors.
I have this when I press one of the buttons of my custom "tab bar" (seconviewcontroller)
ConfiguracionViewController *conf = [[ConfiguracionViewController alloc] initWithNibName:#"ConfiguracionView" bundle:nil];
[self.navigationController pushViewController:conf animated:YES];
[conf release];
but because the second view controller is not pushed or modal presented in the first view controller I can't acces the navigationController. I've tried also with this
[self.parentViewController.navigationController pushViewController:...];
But it didn't work either.
Please help me out, I need to learn how to do that and sorry for my bad english.
Best Regards,
Carlos Vargas
First, shouldn't you be adding the second view to the first view like this:
[self.view addSubview:second.view];
The property parentViewController will not work in this case since second is not part of a navigation hierarchy.
Instead, you can make your own property that references the "parent" view controller:
SecondViewController *second = [[SecondViewController alloc] initWithNibName:...];
// set new property
second.parentVC = self;
[self.view addSubview:second.view];
In SecondViewController.h you need to declare the instance variable and property for "parentVC" and in SecondViewController.m you need to synthesize the property.
Then, you should be able to access the navigation controller and push a view controller from SecondViewController.m like this:
[self.parentVC.navigationController pushViewController:...];