Design custom UIView using XCode 4 - xcode

This has been asked before, but I cannot find a definitive answer.
I would like to design a custom UIView class. I would like to do the layout in XCode 4 in a XIB file. Ideally:
I have the files MyView.h, MyView.m and MyView.xib.
The code defines the behavior and the XIB file defines the layout.
The code may have outlets into the XIB file to reference the layout elements.
The loader of the view could be different objects.
I'd like to be able to load the view by:
MyView *v = [MyView myView];
I've tried lots of different methods of setting the File's Owner and loading the XIB via NSBundle, but I keep getting key value coding problems.
Can anyone share the basic method to do this?
I have a repo of my current code here. As you can see, it generates a key value coding error.

Oh, I seem to have figured it out.
Keys are the following:
The XIB file UIView only needs to be a generic UIView. No need to set it to your subclass.
The File's Owner needs to be your subclass.
Outlets go to the File's Owner.
In your custom UIView, load the NIB as follows:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:nil];
UIView *v = [[nib instantiateWithOwner:self options:nil] lastObject];
v.frame = self.frame;
[self addSubview:v];
}
return self;
}

How exactly do you instantiate it? In my "container" class I have an outlet for my custom view as a property, but when I try to do self.customView = [[CustomView alloc] init];
the view does not come on screen, even though I know that it's correctly initialized because I can see the NSLog()s I put in my custom view's initWithFrame: method.
From within my main view controller, BEFORE instatiating the new class, the view is as I've defined it in the storyboard: <CustomView: 0x6a83bb0; frame = (38 153; 244 153); autoresize = RM+BM; layer = <CALayer: 0x6a83c60>>
However, inside the custom view's initWithFrame:, the frame is {{0, 0}, {0, 0}} and the view is <UIView: 0x68ca160; frame = (0 0; 0 0); autoresize = W+H; layer = <CALayer: 0x68d1800>>.
After the initialization has taken place, the main view's property for the custom view is barely (null), which baffles me.
I'm sure I'm missing something very simple here. Are you even supposed to define the custom view's within the main view in the storyboard at all?
Thanks!

Related

NSSplitViewController/NSSplitViewItem support in XIBs

Is there support for NSSplitViewController/NSSplitViewItem for XIBs? I see only NSSplitView
Can I just drag&drop NSViewController and subclass it as NSSplitViewController? How do I add NSSplitViewItem that it mostly works out of the box?
I can easily see support for them in storyboards.
The split view controller is not part of the object library for xib files. The easiest way to use split view controllers is to use storyboards.
If you are unwilling to use storyboards, your best option is to create a subclass of NSSplitViewController and select the checkbox to also create a xib file.
Add a split view to the split view controller xib file. Write code to load the xib file to set up the split view controller.
UPDATE
Look at the NSNib class reference for information on loading a xib file. The File's Owner of the xib file is your NSSplitViewController subclass. You may be able to use that information to set the split view controller. The worst case scenario is that you have to write code to load the split view from the xib file, set the split view controller's split view to the split view you loaded, and add the split view items to the split view controller. See the NSSplitViewController class reference for more information.
Yes it's possible. But it needs some wiring.
First add a custom subclass of NSSplitViewItem and expose viewController property as IBOutlet. Compiler will throw a warning so don't forget to mark property as dynamic.
#interface MySplitViewItem : NSSplitViewItem
#property IBOutlet NSViewController *viewController;
#end
#implementation MySplitViewItem
#dynamic viewController;
#end
In your XIB add 3 NSViewController objects. One of them change to custom class NSSplitViewController. It is important to note that one should NOT add NSSplitView. Wire NSViewControllers to it's views. Also add 2 objects and add custom class of MySplitViewItem which has exposed the viewController and wire it.
Last step. It is important to set property splitItems of NSSplitViewController before the views are loaded! Otherwise you are caught with NSAssert macro.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSNib *nib = [[NSNib alloc] initWithNibNamed:#"Empty" bundle:nil];
NSMutableArray *test = [NSMutableArray new];
NSMutableArray *splitItems = [NSMutableArray new];
NSSplitViewController *controller;
[nib instantiateWithOwner:self topLevelObjects:&test];
for (id object in test) {
if ([object isKindOfClass:[NSSplitViewController class]]) {
controller = object;
}
if ([object isKindOfClass:[NSSplitViewItem class]]) {
[splitItems addObject:object];
}
}
[controller setValue:splitItems forKey:#"splitViewItems"];
[[self window] setContentViewController:controller];
}
Here is a proof that everything is wired correctly. Note that I did not touch delegate in XIB and it is wired. Magic, I know.
PS: XIB has to be set to prefer Coder + auto layout.
Why do I prefer XIB? Because we can create larger XIB which doesn't suffer from data isolation (Easily can do bindings across NSViewControllers).
I have also experimented to add splitViewItems in viewDidLoad or setView or awakeFromNib: in custom subclass of NSSplitViewController (with exposed NSSplitViewItem properties). If someone finds solution here it will be greatly appreciated.
Solution that requires code only:
- (NSSplitViewController *)profilesSVC
{
if (!_profilesSVC) {
NSSplitViewController *splitVC = [[NSSplitViewController alloc] init];
ProfilesViewController *profilesVC = [[ProfilesViewController alloc] initWithNibName:#"Profiles" bundle:nil];
NSSplitViewItem *leftItem = [NSSplitViewItem splitViewItemWithViewController:profilesVC];
[splitVC addSplitViewItem:leftItem];
ProfileViewController *profileVC = [[ProfileViewController alloc] initWithNibName:#"Profile" bundle:nil];
NSSplitViewItem *rightItem = [NSSplitViewItem splitViewItemWithViewController:profileVC];
[splitVC addSplitViewItem:rightItem];
_profilesSVC = splitVC;
}
return _profilesSVC;
}
I too wanted to add a splitView controller to my projet (macOS app) that doesn't use storyboards.
As it turned out, this was rather easy (in XCode 12.4).
As suggested, one has to to add NSViewController objects to the xib and wire each view property to the corresponding 'pane' (subview of the split view) in interface builder.
Then create a subclass of NSSplitViewController (no need to create a xib file).
Add a third NSViewController object to the xib and change its class to your subclass. Then wire both it's view and splitView properties to your splitView. It doesn't load any view if you just wire the splitView property.
Using a subclass of NSSplitViewController may not be required, but it's convenient as you may set the splitViewItems within viewDidLoad (below). Since this object is (automatically) the delegate of the splitView, you can also override delegate methods if you wish.
That object should have outlets leading to the NSViewController objects which you previously wired to the panes in IB.
I set two outlets named leftController and rightController.
My awakeFromNib method looks like this (sorry, I don't use swift):
- (void) viewDidLoad {
self.splitView.wantsLayer = YES; // I think this is required if you use a left sidebar with vibrancy (which I do below). Otherwise appkit complains and forces the use of CA layers anyway
NSSplitViewItem *left =[NSSplitViewItem sidebarWithViewController:leftController];
[self addSplitViewItem:left];
NSSplitViewItem *right =[NSSplitViewItem splitViewItemWithViewController:rightController];
right.minimumThickness = 420;
[self addSplitViewItem:right];
}
Voilà!
However, I get crashes if I set thick dividers in IB as appkit calls splitView:shouldHideDividerAtIndex too early, when there is apparently no divider yet. Worse, it may pass a negative divider index (!!). But you may override the method and act accordingly and I have no issue with thin dividers.

Interface-Builder: "combine" NSView-class with .xib

I'd like to set up a custom NSView in Interface-Builder, but I don't get it to work for OSX.
In my ViewController's .xib, I added a custom view and set the Class to MyCustomView. I created MyCustomView.h, MyCustomView.m and MyCustomView.xib.
In MyCustomView.xib, I set the Class to MyCustomView as well. In MyCustomView.m, - (void)awakeFromNib is called, but - (id)initWithCoder:(NSCoder *)aDecoder and - (id) awakeAfterUsingCoder:(NSCoder*)aDecoder aren't.
What I'd like to achieve is that in my ViewController, the view I added is "filled" with the view I set up in MyCustomView.xib. What's the best way to do that?
EDIT: I don't think I was clear enough...
I've got my ViewController containing a Custom View called MyCustomView.
This view should be of type MyCustomView, where
MyCustomView.h
MyCustomView.m
MyCustomView.xib
exists. I already set the File's Owner of MyCustomView.xib to MyCustomView and I already set the CustomView in my ViewController to MyCustomView - but it doesn't work.
If I do it with
- (void)awakeFromNib {
NSString* nibName = NSStringFromClass([self class]);
NSArray* topLevelObjects;
[[NSBundle mainBundle] loadNibNamed:nibName
owner:nil
topLevelObjects:&topLevelObjects];
NSView* view = topLevelObjects[0];
[view setFrame:[self bounds]];
[self addSubview:view];
}
I only get a view of type NSView, not MyCustomView... Is there no easy way to tell the ViewController.xib that it's a MyCustomView?
EDIT 2: I uploaded a simple project
At https://dl.dropboxusercontent.com/u/119600/Testproject.zip you find a simple project with the MyCustomView (not in a ViewController but in the window.xib) - but it doesn't show the button which is in MyCustomView.xib. I'd like to achieve exactly that - what's the simplest, best way?
EDIT - apologies, my existing answer failed to take into account the need to connect outlets and actions. This way should do it...
Given the files...
MyCustomView.h
MyCustomView.m
MyCustomView.xib
In MyCustomView.h
declare IBOutlets for your interface elements. You need at least one, to hold a pointer to the top-level view in the xib file
#property (nonatomic, strong) IBOutlet NSView *view;
In MyCustomView.xib
ensure that there is only one top-level view
set file's owner class to MyCustomView in the Identity Inspector
ensure that the top-level view is set to the default NSView class.
now you can connect IBOutlets declared in MyCustomView.h to interface objects in the xib file. At very least you need to connect up the top-level view to your view outlet.
In MyCustomView.m:
- (id)initWithFrame:(NSRect)frame
{
NSString* nibName = NSStringFromClass([self class]);
self = [super initWithFrame:frame];
if (self) {
if ([[NSBundle mainBundle] loadNibNamed:nibName
owner:self
topLevelObjects:nil]) {
[self.view setFrame:[self bounds]];
[self addSubview:self.view];
[self.myCustomButton setTitle:#"test success"];
}
}
return self;
}
In your window's xib file, add a custom NSView and change it's class to MyCustomView.
in OSX prior to 10.8 the method loadNibNamed was a class method - use it instead if you need backwards compatibility, but it is deprecated now:
[NSBundle loadNibNamed:#"NibView" owner:self]
Note that MyCustomView.xib's view is NOT MyCustomView's view, but the sole subview of it's view (this is similar to the way a tableViewCell possesses a single contentView).
In the project sample you have posted, you need to make the following changes:
in MyCustomView.h
. add an NSView property
in MyCustomView.xib:
. change the top-level view from MyCustomView custom class to NSView (the default)
. set the File's Owner to MyCustomView.
. connect IBOutlets from File's owner's view and myCustomButton to interface view and button
. for testing make the view a lot smaller and push the button up to the top right (you won't see it in your window as it is here)
in MyCustomView.m:
. replace all of your implementation code with the initWithFrame method here
In order to load a custom subclass of a view or a control (or any other class that can be used in Interface Builder for that matter), you need to add the base version (NSView in your case, and as you have done), then select that object in the window and go to the Identity Inspector (Cmd-Opt 3).
Instead of the pre-defined value for Class (NSView, in your case), type in the name of your custom subclass. Voilà!
A small detail related to the IB UX is that you'll probably have to move the focus from the input field in order for that change to be registered when you build and run the app.

Adding CPTGraphHostingView as a subview

There isn't a lot of questions about this and none of them were answered in a way that could solve my problem, still sorry if I'm asking something that was asked already.
My problem is that I'd like to use core plot to draw a graph inside a view. So I add the view on IB in my appviewcontroller.xib , but thenI can't find how to link that specific zone I just defined on IB to the file's owner.
A bit of precision: I managed to use core plot in another application, when it was the main view, but now that I want to add it to an existing application, so an existing view, it doesn't work...
And to add the new zone, I just went into IB, and dragged a view from the objects library onto my appviewcontroller.xib.
So how do I link that particular view to the code I wrote for it ?
Thanks for any help !
U have to add uiview created to self.view,then add graph hosting view to it...
Try this
- (void)loadView {
self.view =view1;
}
- (void)viewDidLoad {
[super viewDidLoad];
CPGraphHostingView *hostingView = [[CPGraphHostingView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
hostingView.backgroundColor=[UIColor whiteColor];
graph = [[CPXYGraph alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
hostingView.hostedGraph = graph;
[view1 addSubview:hostingView];
Create another view(view 2) in a main view(default view let it be view)in the storyboard(xib file) and make sure it (view 2) as outlet in the header file(.h).
Drag and Connect it to the newly created view in the storyboard(xib file) so after making necessary connections in the storyboard,storyboard should be like this:
View---view (default view connected to view) and
view 2---view (new view, view 2 connected to its view).
In the loadView method of implementation file,try out these code
-(void)loadView {
[super loadView];
newView = [[CPTGraphHostingView alloc] initWithFrame: [[UIScreen mainScreen]applicationFrame]];
[self.view2 addSubview:newView];
}

How do I load a nib file from Code?

I created a custom view in interface builder with a few buttons in it. I created a class in code for it as the "Files owner" to connect the buttons to action methods.
How do I use this class then?
I cannot just do it like this...
StartScreen *ss = [[StartScreen alloc] initWithFrame: ...];
[self.window.contentView addSubView: ss];
...
because this only produces an empty view. (of course: the StartScreen class doesn't know anything about the nib file yet.)
What I want to do is something like:
StartScreen *ss = LoadCustomViewFromNib(#"StartScreen");
[self.window.contentView addSubView: ss];
or maybe i have to say something like
[self iWannaBeANibWithName: #"StartScreen"];
in the constructor of StartScreen?
pls help...
(btw I am developing for Mac OS X 10.6)
One option is to make StartScreen a subclass of NSViewController, maybe changing its name to StartScreenController. This is a potentially more modular solution in case you have IBActions in your nib file and/or you want to place view controlling code in its own class.
Declare StartScreenController as a subclass of NSViewController
Declare IBOutlets in StartScreenController if needed
Set the nib file’s owner class to be StartScreenController
Connect the file’s owner view outlet to the view object, and other outlets if needed
Then:
StartScreenController *ss = [[StartScreenController alloc] initWithNibName:#"nibname" bundle:nil];
[self.window.contentView addSubView:ss.view];
…
If you’re not using garbage collection, don’t forget to release ss when it’s not needed any longer.
The Nib loading functions are part of the NSBundle class. You can use it like this...
#implementation StartScreen
- (id) init {
if ((self = [super init])) {
if (![NSBundle loadNibNamed:#"StartScreen" owner:self])
// error
// continue initializing
}
return self;
}
See NSBundle Additions reference.

Custom header and footer in an iPhone table view

I'm trying to implement something just like the HeaderFooter sample code that apple provides:
Unfortunately they used Interface Builder to do most of the work here, and I'm beginning to see that interface builder is the hardest thing to deal with in iPhone development.
My table works fine, but the header and footer aren't there at all. My question is how can I make it work.
My code is identical to Apple's, with a few name changes:
- (void)viewDidLoad
{
// headerView
CGRect newFrame = CGRectMake(0.0, 0.0, self.tableView.bounds.size.width, self.headerView.frame.size.height);
self.headerView.backgroundColor = [UIColor clearColor];
self.headerView.frame = newFrame;
self.tableView.tableHeaderView = self.headerView; // note this will override UITableView's 'sectionHeaderHeight' property
// set up the table's footer view based on our UIView 'myFooterView' outlet
newFrame = CGRectMake(0.0, 0.0, self.tableView.bounds.size.width, self.footerView.frame.size.height);
self.footerView.backgroundColor = [UIColor clearColor];
self.footerView.frame = newFrame;
self.tableView.tableFooterView = self.footerView; // note this will override UITableView's 'sectionFooterHeight' property
//...
The problem must be in my xib file, but the sample file has one giant nib that defines everything, all the way back to the window so I can't use that. I had to make my own.
I started with a uitableviewcontroller and nib and added header and footer views views to the nib.
I connected them to their respective IBOutlets in the table view controller subclass.
The delegate and datasource are connected and all the table cells work fine.
What did I forget? Why isn't it making the header and footer?
Follow up question:
How can I learn how to use IB? I try to avoid it, but sometimes I can't. It seems really poorly documented.
It worked when I used initWithNibNamed: to create the view. I'm not really sure why. It seems to use the .nib when I use initWithStyle:. I have to admit, I'm still fuzzy on the relationship between UIViewControllers and .nibs…

Resources