-2

In my experience, the most robust view/viewcontroller implementations do all of their element creation and layout directly in the UIView subclass (in initWithFrame: and layoutSubviews), rather than using a .xib file. While one can certainly use a .xib file to achieve the same result, the complexity and maintenance doesn't seem worth the trouble. I recently started working on a custom view controller and UIView subclass, for which I am using the PFLogInViewController from the ParseUI framework as a reference to create this '.xib-free' robustness that I need.

My custom UIView subclass (which I will call NumPadView as an example) is responsible for laying out buttons, labels, and other UI elements, while my custom UIViewController subclass (which I will call NumPadViewController as an example) is responsible for creating the NumPadView instance and adding target actions to all of its buttons, and then implementing those actions. Here is an example interface for NumPadView:

//
//  NumPadView.h

#import <UIKit/UIKit.h>

@interface NumPadView : UIScrollView
@property (strong, nonatomic, readonly) UIButton *button1;
@property (strong, nonatomic, readonly) UIButton *button2;
@property (strong, nonatomic, readonly) UIButton *button3;
@property (strong, nonatomic, readonly) UIButton *button4;
@property (strong, nonatomic, readonly) UIButton *button5;
@property (strong, nonatomic, readonly) UIButton *button6;
@property (strong, nonatomic, readonly) UIButton *button7;
@property (strong, nonatomic, readonly) UIButton *button8;
@property (strong, nonatomic, readonly) UIButton *button9;
@property (strong, nonatomic, readonly) UIButton *button0;
- (instancetype)init;
@end

Using this interface, NumPadViewController (with a _numPadView property) can do things such as:

self.view = _numPadView;
[_numPadView.button0 addTarget:self selector:@selector(button0Pressed) forControlEvents:UIControlEventTouchUpInside];

Now, to get to my question. Eventually, all of the NumPadView layout will be done in code, similar to the following:

//
//  NumPadView.m

#import "NumPadView.h"

@interface NumPadView ()
@property (strong, nonatomic, readwrite) UIButton *button1;
@property (strong, nonatomic, readwrite) UIButton *button2;
@property (strong, nonatomic, readwrite) UIButton *button3;
@property (strong, nonatomic, readwrite) UIButton *button4;
@property (strong, nonatomic, readwrite) UIButton *button5;
@property (strong, nonatomic, readwrite) UIButton *button6;
@property (strong, nonatomic, readwrite) UIButton *button7;
@property (strong, nonatomic, readwrite) UIButton *button8;
@property (strong, nonatomic, readwrite) UIButton *button9;
@property (strong, nonatomic, readwrite) UIButton *button0;
@end

@implementation NumPadView
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.button1 = [[UIButton alloc] initWithFrame:CGRectZero];
        self.button1.backgroundColor = [UIColor redColor];
        //...
    }
    return self;
}

- (void)layoutSubviews {
    self.button1.frame = //...
    //...
}
@end

However, I always do the layout/UI design as the last step because I want to make sure that everything functions first. I will therefore spend my time implementing methods in NumPadViewController, rather than coding the layout in NumPadView. Of course, to test the functionality of the view controller, I will need some sort of UI to interact with. I would like to make a quick prototype UI using a .xib file and attaching it to the NumPadView. I don't want to change the interface of the NumPadView at all, as that would require changes to my NumPadViewController. So my problem is figuring out how to create and load a .xib from within the NumPadView implementation. Of course, .xibs are typically owned by a view controller and IBOutlets are linked to properties in said view controller, but I want to shift all of that to the private implementation of NumPadView like such:

//
//  NumPadView.m

#import "NumPadView.h"

@interface NumPadView ()
//Link private interface properties to .xib file
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button1;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button2;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button3;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button4;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button5;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button6;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button7;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button8;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button9;
@property (strong, nonatomic, readwrite) IBOutlet UIButton *button0;
@end

@implementation NumPadView
- (instancetype)init
{
    //I'm not sure if this is the right way to do it, but I'm looking for something similar in effect.
    self = [[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil].firstObject;
    return self;
}
@end

So, how can I effectively implement this? I've tried using loadFromNib:, but the button properties always end up being nil. I've connected all of the IBOutlets in the interface builder and set the file owner on NumPadView.xib to NumPadView and the class of the main view to UIScrollView (I've tried NumPadView as well). In addition to advice on how to successfully implement this scheme, I am certainly open better ways of doing this whole prototyping scheme if anyone has a better method. The most important thing is that the NumPadView maintain a consistent interface and that the NumPadViewController has no knowledge of the .xib file. This concept seems to be missed in similar questions such as this one and this one. Thank you for your input; I look forward to reading your answers.

Community
  • 1
  • 1
JWVincent
  • 11
  • 2

1 Answers1

2

You can't quite do what you're doing, because that's not how nib loading works. A nib has a File's Owner. The outlets to the external world, as it were, are to that owner. Thus if a NumPadView is the top-level object in the nib, then a NumPadView cannot be the owner, because what you end up with is another NumPadView (the one in the nib). And if another object is the owner - which is the usual thing - then outlets should not be from the buttons to the File's Owner, which would be pointless, but to the NumPadView in the nib. (There will also usually need to be an outlet from the NumPadView in the nib to the File's Owner so that the owner can retrieve the NumPadView from the nib and do something with it.)

So you need to think about where your outlets are hooked up in the nib, what the File's Owner is (what class), and who the actual owner is at load time. If you structure things accordingly, so that they match up in the internal world of the nib and the external world of the loader, all will be well.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Further discussion, and an example of loading and retrieving an arbitrary object from a nib, here: http://www.apeth.com/iOSBook/ch07.html#_nib_loading_and_file_8217_s_owner – matt Jan 07 '15 at 19:02
  • And of course this sort of thing is what we _always_ do for something like a table view cell in a nib: http://www.apeth.com/iOSBook/ch21.html#_designing_a_cell_in_a_nib – matt Jan 07 '15 at 19:04
  • Do you know of any better ways to prototype UIs like I am trying to do? Thanks. – JWVincent Jan 07 '15 at 19:21
  • What you're doing is great. You just have to structure your nib and loading correctly and consistently with each other, as I said in my answer. – matt Jan 07 '15 at 19:23