I am building a cocoa application with one main window controller with a xib. That xib contains many custom view classes. I would like to add an NSViewController to the xib, but i'm running into some trouble.
In interface builder I can drag the NSViewController into the xib, assign it its custom controller class, and assign its view to the appropriate view in the xib. Here's the problem: neither the initWithNibName:Bundle: or loadView get called.
What am I missing?
EDIT:
People seem to be misunderstanding the question so I'll clarify.
The window already has a view controller. What I am seeking to do is assign separate view controllers to several of the subviews. I need to know how to associate my NSViewController subclass with the appropriate NSView subclass (which is a child of the main window).
Or in other words, I am trying to use multiple NSViewController subclasses to controll many different custom views (one each) within a single .xib file. Those controllers and subviews have their own .xibs which should ultimately become visible in the same window.
The pattern I use for NSViewController is to have a xib per view controller. Then, when you need that view controller you alloc it and use the initWithNibName:Bundle: method. As soon as you use its view, loadView will get called.
Example:
self.editViewController = [[[MyEditViewController alloc] initWithNibName:#"MyEditViewController" bundle: nil] autorelease];
[self.window setContentView: editViewController.view];
I used to get stuck with that as well and gave up on that thing - the blue circle with a white bordered view in it from the IB palette. I now create my controllers from code and only set a reference in IB to the owning controller class via the file owner: right click the file owner, enter the class name in the Identity inspector and then make a connection from the file's owner view to the view.
In your code you then do at an appropriate initialisation point:
[self setMyViewController = [[MyViewController alloc] initWithNibName: #"MyView" bundle: [NSBundle mainBundle]]
For your specific case this could be in windowDidLoad method when your window is loaded from its nib and ready for work. You can then add the view to your windows content view. Also you might want to consider to have a 1:1 relation between view and view controller. It makes life a lot easier in terms of maintenance.
EDIT: Like #pcperini suggests in his comments you can use the palette component, but you'll still need to instantiate the controller in your code. If you want to use the palette component, create a property in your main controller or AppDelegate:
#property (...) MyViewController *myViewController;
Add the line of code to actually create the controller (see above). Then, using the bindings inspector bind the palette component to the myViewController property.
So, what you are missing is that you are actually not instantiating the controller object.
EDIT 2: Here's the code (the awakeFromNib is the method of the top controller). It creates two child controllers each handling a different subview:
- (void) awakeFromNib {
[[self startEndTopicHeader] setHeader: #"Event timeline boundary"];
[[self startDateHeaderView] setHeader: #"Event (start) date"];
[[self endDateHeaderView] setHeader: #"Event end date"];
[self setStartDateViewController: [[EventTimeViewController alloc] initWithNibName: #"EventTimeView" bundle: [NSBundle mainBundle]]];
[[[self startDateViewController] view] setFrame: [[self dummyStartView] bounds]];
[[self dummyStartView] addSubview: [[self startDateViewController] view]];
[[self startDateViewController] setParentController: self];
[self setEndDateViewController: [[EventTimeViewController alloc] initWithNibName: #"EventTimeView" bundle: [NSBundle mainBundle]]];
[[[self endDateViewController] view] setFrame: [[self dummyEndView] bounds]];
[[self dummyEndView] addSubview: [[self endDateViewController] view]];
[[self endDateViewController] setParentController: self];
}
Related
I have xib with two CustomView (NSView *one, NSView *two),how i addSubview in AppDelegate?
self.content = [[ContentViewController alloc]
initWithNibName:#"ContentViewController"
bundle:nil];
[[[[self vertical] subviews] objectAtIndex:1] addSubview:[_content one]];
This way don't work.
Each view should be in it's own NIB file, as the NSViewController only has a single view instance variable.
So the answer is to split each view into it's own NIB; set the custom class correctly and then set the File Owner to NSViewContoller and connect the view from the controller to the custom view.
You then load each separately and add their views whereever you like (taking care to keep a reference to the NSViewController used to load the view).
I'm attempting to create a subview inside a regular view. When a button is clicked that is contained in this subview, it will flip over and show a new subview. This will is meant to simulate a card flipping over on a table. I have a DetailViewController that is an empty xib, but when it's loaded it loads another controller called CardFrontViewController that actually contains the subview (front of the card) all contained in a xib. Once the button inside the subview, it should load the subview that is contained inside the CardBackViewController xib.
Inside DetailViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if (!self.cardFrontViewController)
{
self.cardFrontViewController = [[CardFrontViewController alloc] initWithNibName: #"CardFrontViewController" bundle: nil];
[self.view addSubview: self.cardFrontViewController.view];
}
[self configureView];
}
Inside CardFrontViewController
- (IBAction)flipToAnswerCard:(id)sender
{
if (!self.cardBackViewController)
{
self.cardBackViewController = [[CardBackViewController alloc] initWithNibName: #"CardBackViewController" bundle: nil];
}
[UIView transitionWithView: self.cardFrontContainerView
duration: 1.0
options: UIViewAnimationOptionTransitionFlipFromTop
animations: ^{ [self.cardFrontSubView removeFromSuperview];
[self.cardFrontContainerView addSubview: self.cardBackViewController.cardBackSubView]; }
completion: nil];
}
When I click the button to flip over the card, the card flips, but the view that is displayed is empty, meaning that the self.cardBackViewController.cardBackSubView is not loaded inside.
If I do [self.cardFrontContainerView addSubview: self.cardBackViewController.view] inside the flip card method it loads the entire xib inside, but I really just want a view inside. I'm not sure what to do here.
You don't need a UIViewController if all you want is just to load a view, with some IBOutlets.
Just use [[NSBundle mainBundle] loadNibNamed:#"myViewNib" owner:self options:nil] from your DetailViewController, and if IBOutlets corresponding to ne 'card' NIB exist, they will automatically be linked (to the owner).
If you really want a different class to handle this 'card' (and 'own' the IBOutlets) does it necessarily need to inherit from UIViewController? NSObject can sometimes be a good candidate too.
Finally, If you really really want to nest UIViewControllers, this will only work in iOS5.
To support previous iOS versions, you would eventually have to manually call some UIViewController's life-cycle methods (willAppear, didAppear...), pass certain events from you master UIViewController to its detailViewController (like willAnimateToRotation), and maybe some other tricks.
If you just want to flip subviews within a main view, the answer that worked for me was found here: https://stackoverflow.com/a/9028156/1319615
I am looking for a way to switch the current view in a tab container to another, all within the same tab and not using a navigation controller.
I have tried something like this:
FooViewController *fooViewController = [[FooViewController alloc] initWithNibName:#"FooViewController" bundle:nil];
self.view.window.rootViewController.view.window.rootViewController = fooViewController;
[fooViewController release];
And this:
FooViewController *fooViewController = [[FooViewController alloc] initWithNibName:#"FooViewController" bundle:nil];
[self.view removeFromSuperview];
[self.view addSubview:fooViewController.view];
[fooViewController release];
To no avail.
Any ideas?
The method I used was to create a subclass of UIViewController that I used as the root view of 3 child view controllers. Notable properties of the root controller were:
viewControllers - an NSArray of view controllers that I switched between
selectedIndex - index of the selected view controller that was set to 0 on viewLoad. This is nonatomic, so when the setSelectedIndex was called it did all the logic to put that child view controller in place.
selectedViewController - a readonly property so that other classes could determine what was currently being shown
In the setSelectedIndex method you need to use logic similar to:
[self addChildViewController: selectedViewController];
[[self view] addSubview: [selectedViewController view]];
[[self view] setNeedsDisplay];
This worked really well, but because I wanted to use a single navigation controller for the entire application, I decided to use a different approach.
I forgot to mention you will want to clear child view controllers every time you add one, so that you don't stack up a ton of them and waste memory. Before the block above call:
for (UIViewController *viewController in [self childViewControllers])
[viewController removeFromParentViewController];
There is a UIViewController that uses a UIImageView, and that image view is initialized with image data (NSData). It does not use a XIB, but creates its view programmatically:
- (void)loadView
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:self.imageData]];
scrollView = [[UIScrollView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
scrollView.contentSize = imageView.bounds.size;
scrollView.delegate = self;
[scrollView addSubview:scrollView];
}
That data has to be set by another controller which allocs, inits, and pushes this view controller onto the navigation controller:
ImageViewController *imageViewController = [ImageViewController alloc] init];
imageViewController.imageData = someData;
[self.navigationController pushViewController:imageViewController animated:YES];
How do I know that everything that needs to be done, which in this case, is setting the data, is done before loadView is called? Or, do I not know, and I have to create a custom initializer, or somehow call loadView again when the view controller receives the data?
I have faced many similar situations where I was confused about what will happen, such as with UITableViewControllers.
How do I know that everything that needs to be done, which in this case, is setting the data, is done before loadView is called?
Because the documentation mentions that view controllers do not load their views until they are needed. And the view controller's view is not needed before the navigation controller tries to push it on screen.
Besides, the proper place for assigning the imageData to your image view is probably viewDidLoad ("If you want to perform any additional initialization of your views, do so in the viewDidLoad method."). And your loadView method will not do anything visible in its current form. You have to assign a view to the view controller's view property in that method.
loadView will happen when the view property of the view controller is accessed. The code you wrote will work fine, because the first time the view property will be accessed will be somewhere inside pushViewController.
If you wrote this you'd have a problem:
ImageViewController *imageViewController = [ImageViewController alloc] init];
NSLog(#"size = (%.0f, %.0f)", imageViewController.view.frame.size.width,
imageViewController.view.frame.size.height);
imageViewController.imageData = someData;
[self.navigationController pushViewController:imageViewController animated:YES];
because you access the view property in the NSLog. That would cause loadView to get called before imageData was set.
I am trying to work my way through basic iPhone programming and I have a good basic understanding of how Interface Builder works, so I decided to try my hand at doing the views programmatically. I have gone through the ViewController Apple guide and searched everywhere and I cannot seem to find a solution to my problem. This leads me to believe it is a very simple solution, but I am just really banging my head against the wall. Basically all I am trying to do is create a view that gets main window as a subview. I know that if self.view is not defined then the loadView method is supposed to be called, and everything is supposed to be set up there. Here is the current state of my code:
The delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
StartMenuViewController *aViewController = [[StartMenuViewController alloc] init];
self.myViewController = aViewController;
[aViewController release];
UIView *controllersView = [myViewController view];
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[window setBackgroundColor:[UIColor redColor]];
[window addSubview:controllersView];
[window makeKeyAndVisible];
}
The view controller:
- (id)init {
if (self = [super init]) {
self.title = #"Start Menu";
}
return self;
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
UIView *startView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[startView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[startView setBackgroundColor:[UIColor greenColor]];
self.view = startView;
[startView release];
}
Thanks for the help in advance!
Are you sure that you're inheriting from UIViewController and not overriding the implementation of - (UIView*)view?
EDIT: More info:
UIViewController has a special implementation of the "-(UIView*) view" message so that when it's called, the loadView method is called if the view member variable is not set. So, if you provide an implementation of "- (id)view" in your subclass, (or a property named view) it will break the auto-calling of "- loadView".
Just to document a "loadView is not called" case:
I wrote a 2 UITableViewController(s) to handle detail data for a master ViewController. Since the devil was in #2, I made a simple UITableViewController for #1, and referenced it in the XIB for the "master" ViewController.
When I was done with #2, I could simply copy the code to #1, remove the complicated code, and go on with life.
But to my dismay and several days work, no matter what I did, viewLoad was not being called for my simple #1 UITableViewController.
Today I finally realised that I was referencing the UITableViewController in the XIB to the master ViewController program. - and of course, loadView was never being called.
Just to help some other dork that makes the same mistake....
Best Regards,
Charles
viewDidLoad only if the view is unarchived from a nib, method is invoked after view is set.
loadView only invoked when the view proberty is nil. use when creating views programmatically. default: create a UIView object with no subviews.
(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:[UIScreen
mainScreen].applicationFrame];
[view setBackgroundColor:_color];
self.view = view;
[view release];
}
By implementing the loadView method, you hook into the default memory management behavior. If memory is low, a view controller may receive the didReceiveMemoryWarning message. The default implementation checks to see if the view is in use. If its view is not in the view hierarchy and the view controller implements the loadView method, its view is released. Later when the view is needed, the loadView method is invoked
again to create the view.
I would strongly recommend you use interface builder for at least your initial Window/View.
If you create a new project in XCode you should be able to select from one of many pre-defined iPhone templates that come with everything setup.
Unless I am reading this wrong, you did not associate any view with the the controller's view property like this
myViewController.view = controllersView;
So as far as Cocoa is concerned the view you are setting in the window has no controller to call loadView on. loadView is a View controller, not view, method. The view you assign to the window is not associated with any view controller. So your view controller loadView method is never called. Get it? The view you are trying to display, has no view controller associated with it.
When you use interface builder to create views you can link the UIView object you created in IB to the view property in the controller in IB which the framework automatically
But if not done in IB you have to set it