Numerous people have asked a similar question, but the responses were either just vague enough or not in the direction I needed things to go, so I will attempt to ask this as concretely as possible. The context of this question is for iOS development using XCode 4.
I have an app where I use the same Widget numerous times. In this simple exercise the widget will be a fixed sized red box with a label at the top that the root view controller will change, but you can imagine it has lots of ImageViews and ScrollViews and looks pretty spiffy. This last point is key, because non-coding artistic designers want to be able to tweak this view globally without having to do the same thing over and over on each duplicated piece of the UI.
In the app I have replaced the ImageViews and ScrollViews that used to make up the widget with a single UIView, and using InterfaceBuilder set the Custom Class to Widget
. I have outlets in the root view controller for these Widget objects properly hooked up. The Widget Class is a subclass of UIView, and the code is as follows:
@interface Widget : UIView {
UILabel *label;
}
@property (nonatomic, retain) IBOutlet UILabel *label;
@property (nonatomic, copy) NSString *labelName;
@end
@implementation Widget
@synthesize label, labelName;
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
// This is the code that I want to disappear
CGRect pos = CGRectMake(5, 5, 90, 30);
label = [[UILabel alloc] initWithFrame:pos];
[self addSubview:label];
self.backgroundColor = [UIColor redColor];
}
return self;
}
- (NSString *)labelName {
return label.text;
}
// This live updates the label in this custom view
- (void)setLabelName:(NSString *)name {
label.text = name;
}
- (void)dealloc {
[super dealloc];
}
@end
The code works great, and I can live update the text in the custom views from the main view controller. However, the four lines of code that are 开发者_如何学编程in initWithCoder are the problem. Remember the designers? I have them at the point where they can work their magic with Interface Builder, but I can't for the life of me figure out how to get the UIView to load itself from a nib that they have designed. To manually try and replicate the positioning and properties of ever single item is not terribly exciting and pretty time-consuming (and yes I realize I would be done by now if I hadn't written this posting).
So very simply, how can I take an arbitrary .xib file and have it do the work I hand-rolled in initWithCoder? I am assuming it will be trivial at that point to hook up the IBOutlet for the UILabel, but please comment on this if I'm overly optimistic.
As a secondary question, once that is up and running on the real UI, I will have things like, UIScrollViews hooked up. Does Widget become the UIScrollViewDelegate or where does this code get parked. The root view controller should be oblivious to this, but it feels inappropriate to put code I traditionally put in a View Controller into a UIView object.
Declare an IBOutlet (IBOutlet UIView *Widget;) in your controller. Put the Widget view(from the designer) in the xib of your controller(If your controller has no xib, then you can create or specify a new xib for your controller). Then hook up this view to the above IBOutlet. If you want to avoid the hooking of labels, scrollview etc.. whenever the designer makes changes, you have to replace the entire xib instead of just replacing the view.
Yes, you should hook up an IBOutlet
with the label, and then the designers can position the label from Interface Builder.
Regarding UIScrollViewDelegate
, you should also set up an IBOutlet
for the scroll view, and then inside the controller do widgetView.scrollView.delegate = self
, to set your current controller as a delegate of the scroll view.
As an alternative, you can make the Widget
view a delegate of the scroll view, but in my opinion you would break MVC compliance by doing that (the code that handles scroll view events is controller code).
In regard to your first question, I think what you are looking for is this:
Widget *widget = nil;
NSArray *nibItems = [[NSBundle mainBundle] loadNibNamed:@"Widget" owner:self options:nil];
for (id o in nibItems) {
if ([o isTypeOfClass:[Widget class]]) {
widget = (Widget *)id;
}
}
// Do whatever you want with the Widget...
Loading it from the nib via the bundle will preserve your designers work in interface builder, and will trigger a call to initWithCoder.
As for the second question, you have two options. You can either set the delegate property of the UIScrollView within the nib (it is an IB-accessible attribute) or set it manually in code in your initWithCoder method. It depends on what the desired behaviour is.
精彩评论