Link XIB to a custom NSView in a Framework - objective-c

I am writing a Cocoa Framework (not an Application), it contains the definition of a customized NSView; the framework will be loaded in other applications, and the customized NSView will be dragged to the GUI of the applications and become initialized
The question is that I want to include a XIB file in the Framework
I want to add a button and a label to the XIB (in the framework), but the view in the application that consumes the framework, won't show the button and the label
I already set the File's Owner of XIB to the custom NSView in the framework
What else should I do?

Have the view load the nib and move the button, label, etc., from the view in the nib to itself.
It'll be easiest to do this just by getting the subviews of the nib's view and doing this for all of them.
If your nib uses Auto Layout, I think you'll also need to bring across any constraints owned by the nib's view, and you may also need to edit or replace any constraints that refer to that view (e.g., if a view is set to be X points away from an edge of the nib's view).
You may also need to do extra work to change the new view's frame, or to change the frames of the subviews (whether by relocation, resizing, or both) to match the frame given for the new view.

Yeah, I just solved the issue, and I hope the details are helpful to others.
In the framework, there is a complicated subclass of NSView, which contains many controls such as NSSplitView NSOutlineView and IKImageBrowserView, NSPathControl and etc; the framework contains a H file, a M file and a XIB file; H and M define the MyView class; in the XIB, there is a View object whose class is MyView.
On the application side, users need to drag a NSView item onto the main window of their app, and assign an outlet to the view, let's say mainView, and in the applicationDidFinishLaunching function of the "consumer" app, the follow code is necessary
NSBundle *frameworkBundle = [NSBundle bundleForClass:[MyView class]];
NSNib *nibby = [[[NSNib alloc] initWithNibNamed:#"MyView" bundle:frameworkBundle] autorelease];
NSArray *topLevelObjects = nil;
BOOL flag = [nibby instantiateNibWithOwner:nil topLevelObjects:&topLevelObjects];
assert(flag);
for (id topLevelObject in topLevelObjects) {
if ([topLevelObject isKindOfClass:[MyView class]]) {
[mainView addSubview: topLevelObject];
MyView* xView = topLevelObject;
[xView setFrameSize:mainView.frame.size];
break;
}
}
In the code above, the XIB files is loaded, thus the MyView object is initialized, then we fetch it out of the XIB, resize it and add it to the main view of the window

Related

Container View Controllers pre iOS 5

iOS 5 adds a nice feature allowing you to nest UIViewControllers. Using this pattern it was easy for me to create a custom alert view -- I created a semi-transparent view to darken the screen and a custom view with some widgets in it that I could interact with. I added the VC as a child of the VC in which I wanted it to display, then added its views as subviews and did a little animation to bring it on the screen.
Unfortunately, I need to support iOS 4.3. Can something like this be done, or do I have to manage my "alert" directly from the VC in which I want to display it?
MORE INFO
So if I create a custom view in a nib whose file owner is "TapView" and who has a child view that is a UIButton. I tie the UIButton action to a IBAction in TapView.
Now in my MainControllerView I simple add the TapView:
TapView *tapView = [[TapView alloc] init];
[[self view] addSubview:tapView];
I see my TapView, but I can't interact with the UIButton on it and can interact with a UIButton on the MainControllerView hidden behind it. For some reason I am not figuring out what I'm missing...
Not sure if this helps, but, in situations where I've needed more control over potential several controllers, I've implemented a pattern where I have a "master" controller object (doesn't need to be descendent from UIViewController), which implements a delegate protocol (declared separately in it's own file), and then have whatever other controllers I need to hook into declare an object of that type as a delegate, and the master can do whatever it needs to do in response to messages from the controllers with the delegate, at whatever point you need; in your case, that being displaying the alert and acting as it's delegate to handle the button selection. I find this approach to be very effective, simpler and usually cleaner. YMMV ;-)
Regd. your second query, where you are trying to create a custom view using nib. Don't change the FileOwner type, instead set "TapView" for the class property of the top level view object.
Once you have done this, you might experience difficulty when making connections. For that just manually choose the TapView file for making connections.
Also, to load the view you need to load its nib file. For which you can create a class level helper method in TapView class like below
+(TapView *) getInstance
{
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"TapView" owner:self options:nil];
TapView *view;
for (id object in bundle) {
if ([object isKindOfClass:[TapView class]]) {
view = (TapView *) object;
break;
}
}
return view;
}
Now, you get a refrence to you view object like this
TapView *tapView = [TapView getInstance];

How to embed nibs programmatically?

I have a UIViewController with an UIScrollView in it.
Then I created a new UIView subclass with some properties that I want to programmatically add in this UIScrollView until my data ends.
Something that should look like this picture:
I have read other topics that does that by creating a view and setting its class to the custom view subclass, but I want to do it programmatically and its not working.
how would I do that?
From your image it looks like you're looking to load views from a nib and add them as subviews of your UIScrollView. To do this have a look at the UINib documentation.
You want to create your nib and set it's main view to be an instance of your UIView subclass then load the nib in viewDidLoad of your viewController, and add the nib's views as subivews of your scrollview (which I'm assuming is a subview of your viewController's view).
You can instantiate a nib with instantiateWithOwner:options:.
This method unarchives each object, initializes it, sets its
properties to their configured values, and reestablishes any
connections to other objects
To get the array of views from a nib you do something similar to:
UINib *myNib = [UINib nibWithNibName:#"myNib" bundle:[NSBundle mainBundle]];
NSArray *viewsFromNib = [myNib instantiateWithOwner:nil options:nil];
I'll assume we're inside a UIViewController and we're somewhere in (or after) viewDidLoad. You would then use the array from above and add the views as subviews of your scrollview. You may need to set the frames of these views to place them properly, but that should be trivial.
UIView *aView = [viewsFromNib objectAtIndex:0];
[self.scrollView addSubview:aView];
I hope that sets you in the right direction.
Edit:
If you want more information you may need to read deeper into how nibs work to manage your expectation. Linked with the UINib documentation is the 'Resource Programming Guide' in particular the nib section
André, this can be done with relative ease. make sure to import the class that you want to embed. then to create them with your normal
ClassName *subview=[[ClassName alloc]init];
[subview.view setFrame:CGRectMake(x,y,width,height)];
[self.view addSubview:subview.view];
which will add it to the x,y coordinates you specify with the size specified by your width, height. you can do this in the viewDidLoad or whenever you need them to be created.

Cocoa - Display xib on another xib

Can anyone tell me how (or direct me to info on) displaying a .xib (nib) on another .xib (nib).
How ever I wish to place it so I can programically move it around the main nib sort of like this (which obviously doesn't work)
- (void)drawRect:(NSRect)dirtyRect
{
NSRect customView = NSMakeRect(pos1, pos1, 200, 100);
[[NSBundle mainBundle] loadNibNamed:#"secondXib" owner:self];
NSRectFill (customView);
}
And I wish to do this for Mac OS X (Not iPhone). (By the way using xCode 4 incase it makes a difference)
You can easily load a view from another nib using NSViewController. In your nib you should just set File's Owner's custom class to NSViewController and hook up the view outlet of File's Owner to point to the view you want to load. You can then just do this:
//create an NSViewController and use it to load the nib
NSViewController* vc = [[NSViewController alloc] initWithNibName:#"YourNibName" bundle:nil];
//get the view from the view controller
NSView* loadedView = [vc view];
//release the view controller so we don't leak
[vc release];
//add the view as a subview of your main view
[mainView addSubview:loadedView];
//position the view
[loadedView setFrameOrigin:NSMakePoint(100.0, 100.0)];
You don't need to do anything in drawRect:. The subview will draw itself, and drawRect: will be called automatically if you move the subview.
You should read the View Programming Guide for Cocoa. It is critical to understanding how views work, and it is clear from your question that you do not yet have that understanding.
You should also read the Cocoa Drawing Guide.
Thanks a lot,
Another alternative ( which is basically the non programming way of doing it ), is to add a NSViewController object in your first xib, and set it to you use the nib name that you specify.
In your second xib, don't forget to set the class name in the "custom class" field on the view ( and NSViewController on file's owner ) else that won't work.

Object mixup between programmatically created view and interface builder placeholder

I have a view controller which contains a scroll view. Inside the scroll view there is another UI core graphics view. In the view controller I create a temp object for the core graphics view and assign some data to it, then assign it to the attribute of the view controller. Eg: In the view controller:
#interface controller : UIViewController {
GraphView *graph;
}
#property ... IBOutlet GraphView *graph;
#implementation
GraphView *temp = [[GraphView alloc] init];
temp.someArray = anExistingDataArray;
self.graph = temp;
In IB, I open the view controller nib and add a scroll view, and embed a view and assign it the core graphics view class. Then hook up the IBOutlet from that view to the attribute in the view controller.
My problem is that the view controller creates the temp view object, assigns it to itself, with the correct data, however IB seems to instantiate its own object (different memory ID) and displays that, instead of the one in the view controller.
What is the correct way to build this type of setup?
If you drag an object into a nib, IB creates and archives that object. This is why you don't have to alloc/init views that you create in IB.
I'm guessing that you are creating your view in IB so that you can get the geometry correct, or...? It's rather unusual to create a view and then immediately replace it at run-time. More common, for geometric purposes, is to create a container view in IB and then add your programmatically-created views as subviews of that.
Your code left out the most important piece of this, though, which is when it's getting run. Is it in -init...? -awakeFromNib? -loadView? -viewDidLoad? The exact location matters since these occur in a well-defined sequence. If you put your code in the wrong place, it will run before the nib is unarchived and fully reconnected, so the nib will clobber whatever your code did.
So: when is your [self setGraph] (I can't bring myself to use dot syntax) code getting run?

iPhone subview design (UIView vs UIViewController)

I'm designing a simple Quiz application. The application needs to display different types of QuizQuestions. Each type of QuizQuestion has a distinct behavior and UI.
The user interface will be something like this:
alt text http://dl.getdropbox.com/u/907284/Picture%201.png
I would like to be able to design each type of QuizQuestion in Interface Builder.
For example, a MultipleChoiceQuizQuestion would look like this:
alt text http://dl.getdropbox.com/u/907284/Picture%202.png
Originally, I planned to make the QuizQuestion class a UIViewController. However, I read in the Apple documentation that UIViewControllers should only be used to display an entire page.
Therefore, I made my QuizController (which manages the entire screen e.g. prev/next buttons) a UIViewController and my QuizQuestion class a subclass of UIView.
However, to load this UIView (created in IB), I must[1] do the following in my constructor:
//MultipleQuizQuestion.m
+(id)createInstance {
UIViewController *useless = [[UIViewController alloc] initWithNibName:#"MultipleQuizQuestion" bundle:nil];
UIView *view = [[useless.view retain] autorelease];
[useless release];
return view; // probably has a memory leak or something
}
This type of access does not seem to be standard or object-oriented. Is this type of code normal/acceptable? Or did I make a poor choice somewhere in my design?
Thankyou,
edit (for clarity): I'd like to have a separate class to control the multipleChoiceView...like a ViewController but apparently that's only for entire windows. Maybe I should make a MultipleChoiceViewManager (not controller!) and set the File's Owner to that instead?
You're on the right track. In your QuizController xib, you can create separate views by dragging them to the xib's main window rather than to the QuizController's main view. Then you can design each view you need according to your question types. When the user taps next or previous, remove the previous view and load the view you need based on your question type using -addSubview on the view controller's main view and keep track of which subview is currently showing. Trying something like this:
[currentView removeFromSuperView];
switch(questionType)
{
case kMultipleChoice:
[[self view] addSubview:multipleChoiceView];
currentView = multipleChoiceView;
break;
case kOpenEnded:
[[self view] addSubview:openEndedView];
currentView = openEndedView;
break;
// etc.
}
Where multipleChoice view and openEndedView are UIView outlets in your QuizController connected to the views you designed in IB. You may need to mess with the position of your view within the parent view before you add it to get it to display in the right place, but you can do this with calls to -setBounds/-setFrame and/or -setCenter on the UIView.
Yeah, IB on iPhone really wants File's Owner to be a UIViewController subclass, which makes what you want to a bit tricky. What you can do is load the nib against an existing UIViewController instead of instantiating one using the nib:
#implementation QuizController
- (void) loadCustomViewFromNib:(NSString *)viewNibName {
(void)[[NSBundle mainBundle] loadNibNamed:viewNibName owner:self options:nil];
}
#end
That will cause the runtime to load the nib, but rather than creating a new view controller to connect the actions and outlets it will use what you pass in as owner. Since we pass self in the view defined in that nib will be attached to whatever IBOutlet you have it assigned to after the call.