UI Elements and IBOutlet Variables in Storyboard when instantiated recursively? - iphone

What I've done so far:
I am using xcode 4.2. I created a UITableView which contains an ImageView. The UITableView uses the class MyTableViewController which has the files MyTableViewController.m/.h. I created an IBOutlet variable called _bgimageview. Then in the storyboard, in the connections inspector, i connected the ImageView to this _bgimageview variable. In my viewDidLoad function of MyTableviewController.m, I set a backgound image to the _bgimageview. When I run the simulator, click on a few tabs to get to MyTableViewController, and hten I see the background image. Things work great so far.
The problem
I've also successfully built a recursive tree, but the problem is that I lose the backgorund image after the root level of the tree. I think i know why, but don't know how to fix. I instantiate a new MyTableViewController on each row-click like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewController *childView = [[MyTableViewController alloc] init];
childView._menu = children; // an NSDictionary of children
[self.navigationController pushViewController:childView animated:YES];
}
As mentioned previously, I populate the IBOutlet variable for the backgruond image in the viewDidLoad function. But that IBOutlet variable is linked up to the ImageView via the Storyboard connection. In tree levels below the root level, I programmatically instantiate new MyTableViewControllers and thus, I'm probably losing the relationship between the IBOutlet variable for the backgund image and the actual UIImageView mentioned in the Storyboard.
Can someone re-assure me this is the problem and how I might go about adding a backgorund image to MyTableViewController when used in a hierarchical manner?

MyTableViewController *childView = [[MyTableViewController alloc] init];
This line creates a new instance of your view controller without reference to the storyboard. It will therefore not be connecting any of your outlets - the table view will probably be OK, since that is created and connected by default, but anything else will not be connected.
You can confirm this by logging your image view outlet in viewDidLoad - it will be nil.
You should create a recursive stack by dragging a segue from the row of the table down to the view controller object of the same view controller:
As Lvsti points out in comments, you can pass any parameters across in prepareForSegue:sender:

Related

Calling a single view in different UIViewControllers

I have declared a UIView inside a UIViewController class. Can I call this UIView in another UIViewController class?
If its possible how can I call it?
Yes, you can use a single instance of a view in a number of views/viewControllers. Typically I do the same with Views that carry advertisements.
You pass them along as you would do with any other object.
If you do not create it in Interface Bulder (I suggest creating it programmatically) then you may want to define it within your application delegate rather than a view controller and pass it to the individual view controllers that make use of it.
Within the view controller just add it as sub view accordingly, as you would do it with any other view too.
There is one thing though. When you add this view to another super view for the second time or more then it will be removed from its prior super view. That means that you will have to add it as super view again, when its prior super view becomes visible again. A view can only be part of one view hierarchy at a time.
Sample code, thanks to Gordon:
/* Untested and simplified */
AppDelegate.h:
#property ( strong, nonatomic) ReuseableView reuseableView
;
AppDelegate.m
#synthesize reuseableView;
/* in didFinishLaunchingWithOptions ...*/
reuseableView = [[alloc] init]; // or init from nib, initwithframe, etc.
viewController.m
/* In each view controller that uses the view */
- (void) viewWillAppear:(BOOL)animated
{
[self.view addSubview:((AppDelegate*)[UIApplication sharedApplication].delegate).reuseableView];
}
- (void) viewWillDisappear:(BOOL)animated
{
[((AppDelegate*)[UIApplication sharedApplication].delegate).ReuseableView removeFromSuperview];
}
I am not quite sure whether this removeFromSuperview is really required. The next addSubview will remove it from its existing superview anyway and if addSubview is called on the same super view twice in a row then it does not do any harm. However, it is save using removeFromSuperview at this point.
Well, summarized that is basically it. You define, create and store your shared view (the reusableView in Gordon's example) at a common place. The application delegate is a good place for that. In each view's controller that uses the shared view you fetch it from the delegate and add it as subview to the current view.
I would subclass UIView and import it on the ViewControllers where I wanna use it
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"YourView" owner:self options:nil];
yourView = (YourView *)[nibArray objectAtIndex:0];
Then you set its frame and [self.view addSubview:yourView]

Question about the mechanics of iPhone view controllers (i.e., explain why this crashes)

I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)

I can make UINavigationController load only at 2nd level, not at Root View Controller

I tried looking for a similar problem but I could not find a similar question.
I am loading a UINavigationController in a UIView which is not (as in most examples) the MainWindow.
I created one new .xib called DocumentsViewController which is subclass of UIView (it has the related .m and .h files). And I created a DocumentsRootViewController.xib, which is a subclass of UITableViewController, which is supposed to be the UINavigationController's RootViewController.
I moved to DocumentsViewController and added a UINavigationController object in Interface Builder. Then I went to code, and added it as in IBOutlet, and connected it to the object.
In the ViewDidLoad, I execute the following lines:
DocumentsRootViewController *rootViewController = [[[DocumentsRootViewController alloc] init] autorelease];
rootViewController.title = #"Documents";
[navigationControllerDocuments initWithRootViewController:rootViewController];
[self.view addSubview:navigationControllerDocuments.view];
It shows the table as intended, but it shows a "Back" button to the "Root View Controller" (as in picture below).
Why? Shouldn't it already know that the rootviewcontroller has been set?
Thank you in advance to the ones that clarify this doubt
Giovanni
When you add the UINavigationController via the Nib it actually creates an instance of a UINavigationController inside the nib file with a default RootViewController set (of type UIViewController) and with a default title of RootViewController.
When you load the nib, this object is being created as part of loading the nib (i.e when you initialise DocumentsViewController) - so the navigationControllerDocuments outlet is already initialised as a UINavigationController with the default ViewController already set on it.
What I think is happening is when you call 'initWithRootViewController' - you are calling this on an already initialised object - so it is running the initialisation code again - pushing the second view controller (the DocumentRootViewController) onto the stack, but the default one that was created in the nib is already there.
What you should probably do is forget about creating one in the nib and initialise the whole thing programatically.
i.e. where you do:
[navigationControllerDocuments initWithRootViewController:rootViewController];
I suggest that you do an alloc and init instead:
[[navigationControllerDocuments alloc] initWithRootViewController:rootViewController];
Since you are doing this you really don't need to have the navigation controller added to the nib so if this works you should remove it from the nib since you are replacing it with this one in code.

UITableviewController losing reference to tableview

I have been working on this for 2 days now, cant seem to get a grasp. I'm missing something very basic I guess.
Here's what I have:
A UIViewController as the Apps root controller.
There's a ContainerView, a subclass of UIView which I add to my root controller view.
Within that I want a UITableView.
Since there are several different Containers, I have different Nibs for each.
Heres how its wired: Nib with content, has the container as its file's owner. There's an outlet to the UITableView, it has the container as source and delegate.
The container implements the protocol methods.
Now I can't call reloaddata on the UITableView since it's nil. I type po in the consolo and it says 0x0 but I don't know why.
I have been trying different approaches, but all ended up in losing the reference to the tableView.
It's not like it's my first tableview I create but I have no clue on what I'm doing wrong here.
Any HELP please!!!!
Code:
This is my Outlet:
IBOutlet UITableView *contactsTV;
File's owner has a connection to it, the tableview vice versa.
I create the nib by doing:
Contentview *v = [[Contentview alloc] initWithFrame:[[contentViewArray objectAtIndex:i] CGRectValue]];
while contentViewArray is some array storing Framevalues as strings.
Then I do:
[v prepareView];
and it looks like this:
- (void) prepareView {
NSArray *mediaPlayerViews = [[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
UIView *v = (UIView *)[mediaPlayerViews objectAtIndex:0];
[self addSubview:v];
}
Just experienced a similar issue -- everything appeared wired up correctly (Xcode 4) but the outlet reference was nil.
When I created the file, I used the "New File" -> subclass of UITableViewController. with NIB (automatic) process to set up the file. This resulted in the controller being declared as a UITableViewController and the NIB had a UITableView as it's root.
Although the TableDataSource and TableDelegate methods got called as expected, the outlet for the TableView was never being set when the nib was loaded.
To fix this, I basically had to change the controller from being a subclass of UITableViewController to just UIViewController and set the NIB accordingly: I cleared the NIB, added a UIView with a UITableView as a child, reconnected the outlets (View, TableView, TableDataSource, and TableDelegate), and it all worked as planned.
I think this may be a bug with XCode, when creating a subclass of UITableView with NIB.
Set it as a UITableViewController it should work.
Do you have property set in header file along with
IBOutlet UITableView *contactsTV
?
If you don't have setter method for your contactsTV, then your contactsTV isn't retained by your object.
Try to replace your code with
//header file
UITableView *contactsTV;
#property(nonatomic, retain) IBOutlet UITableView *contactsTV;
//implementation file
#synthesize contactsTV;

iPad UISplitViewController multiple root views

I am developing for iPad and have created a standard UISplitViewController application using the template provided in Xcode - a standard UITableView on the left and a detail view on the right.
I have modified the template so that when a user selects a table cell from the left view it pushes a new table view in it's place (still on the left side). This works without issue, but I would like to be able to update the existing detail view from the new table view - kinda like how Apple's Mail application works.
- I am not trying to create multiple views on the detail view (right hand side) - I've read the documentation and seen the sample code provided by Apple.
I read/followed many tutorials, but can't seem to get this relatively simple view hierarchy to work.
More detail:-
Using detailViewController.detailItem = #"Test"; in the RootView didSelectTableRowAtIndexPath delegate method updates the Detail view label. Using the exact same code in the newly pushed Table View does not update the label - am I missing a reference point or something??
Since posting I've tried to use protocols & delegates to update a label on the detail view. The label updates correctly when changed from the Root View using the new methods, however, when I push a new view onto the root view (left hand side) I can no longer update the label.
At some point after creating the RootViewController (or maybe even in a custom init method) you are setting the delegate for the DetailViewController, its a common mistake that when a new rootViewController is pushed onto the NavController that you forget to set the delgate again.
You probably are creating a new controller in the:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
method and recording/incrementing the level of the new controller before you push it onto the navController. After you create this new controller, set the delegate again.
newRootController.myDelegate = self.myDelegate;
Before you do this If you NSLog the delegate just before you use it, you will probably find its nil.
Try the viewControllers property of your UISplitViewController
#property(nonatomic, copy) NSArray
*viewControllers Discussion The array in this property must contain exactly
two view controllers. The view
controllers are presented
left-to-right in the split view
interface when it is in a landscape
orientation. Thus, the view controller
at index 0 is displayed on the left
side and the view controller at index
1 is displayed on the right side of
the interface.
The first view controller in this
array is typically hidden when the
device is in a portrait orientation.
Assign a delegate object to the
receiver if you want to coordinate the
display of this view controller using
a popover.
Please beware of the detailViewController! You have to pass this instance variable to your new root view. So something like this:
newRootViewController.detailViewController = self.detailViewController
Otherwise your new root view will never know about the detailView. For your new root(table)view you have to do things like:
#import <UIKit/UIKit.h>
#class DetailViewController;
#interface VorhersageTable : UIViewController {
UITableView *vorhersageTableView;
DetailViewController *detailViewController;
}
#property (nonatomic, retain) IBOutlet UITableView *vorhersageTableView;
#property (nonatomic, retain) DetailViewController *detailViewController;
#end
to declare the property of the detailViewController in your new class.
Add this in your RootViewController.didselectRow, before you push the second table (e.g SubRoot)
SubRoot *subController = [[SubRoot alloc] initWithNibName:#"SubRoot" bundle:nil];
subController.detailViewController = self.detailViewController;
And create the SubRoot.h and SubRoot.m similar to RootViewController.
#class DetailViewController;
#interface SubRoot : UITableViewController {
DetailViewController *detailViewController;
}
#property (nonatomic, retain) DetailViewController *detailViewController;
#end
then synthesize detailViewController.
Hope it helps.