开发者

Same UIView subclass in several UIViewController subclasses?

开发者 https://www.devze.com 2023-03-19 14:12 出处:网络
I have several UIViewControllers. I would like to use the same UIView subclass (which goes on top of the existing UIViewController\'s view) in all of them. Is this possible using Interface Builder?

I have several UIViewControllers. I would like to use the same UIView subclass (which goes on top of the existing UIViewController's view) in all of them. Is this possible using Interface Builder?

I mean, I would like to be able to drag开发者_Python百科 a UIView onto each UIViewController's view, and rename the class of this dragged UIView to CustomView, and all the elements within the CustomView would show up... is this possible?


Based on your question and your response to highlycaffeinated — whose answer is correct but I think may be slightly askew from what you're asking — I think you want to be able to design a view graphically within Interface Builder (so, you haven't written a custom UIView subclass, you've just arranged some UIViews in a certain way and so that they're all children of another view), then embed it into several view controllers via some sort of indirect reference, so that you're not copying and pasting the same user interface elements and if you make a change in one place, that change then takes effect everywhere?

As far as I'm aware, there's no built-in facility in Interface Builder or Xcode 4 for achieving that. XIBs are pure data and UIViews don't have the smarts to handle an out-of-file reference.

What you can do is design the view you want to use in one XIB, called say ReusableView.xib, then write a custom UIView subclass that looks something like:

@implementation ReusableViewTemplate

- (id)initWithCoder:(NSCoder *)aDecoder
{
    // initialise ourselves normally
    self = [super initWithCoder:aDecoder];

    if(self)
    {
        // load everything in the XIB we created
        NSArray *objects = [[NSBundle mainBundle] 
                            loadNibNamed:@"ReusableView" owner:self options:nil];

        // actually, we know there's only one thing in it, which is the
        // view we want to appear within this one
        [self addSubview:[objects objectAtIndex:0]];
    }

    return self;
}

@end

Then, in your NIBs put in a UIView where you want the reusable view to go, and set the 'class' to 'ReusableViewTemplate' or whatever you called it.

If you open the ReusableView XIB and set the type of the parent view to ReusableViewTemplate, then you can wire up any UIControls (such as buttons or switches) to connect to there. You'll probably want to define some custom protocol for your reusable view template and catch viewDidLoad in any view controllers that use the reusable view in order to set an appropriate delegate.

EDIT: further thoughts on this. I've created an example project (currently at a generic file sharing site, so may not survive forever) with a class ReusableView that, for the purpose of example contains a segment view and a button, and looks like this:

@implementation ReusableView

/*

    initWithCoder loads the relevant XIB and adds its
    only object, which is a UIView, as a subview of this
    one. If you don't like the double hierachy, you
    could just have a list of views in the XIB and
    addSubviews:, but then it'd much more difficult to
    edit the thing graphically. You could strip the top
    view programmatically, but this is just a simple
    example, so...

*/
- (id)initWithCoder:(NSCoder *)aDecoder
{
    // initialise ourselves normally
    self = [super initWithCoder:aDecoder];

    if(self)
    {
        // load everything in the XIB we created
        NSArray *objects = [[NSBundle mainBundle] 
                                loadNibNamed:@"ReusableView"
                                owner:self
                                options:nil];

        // actually, we know there's only one thing in it, which is the
        // view we want to appear within this one
        [self addSubview:[objects objectAtIndex:0]];
    }

    return self;
}

@synthesize delegate;
@synthesize segmentedControl;
@synthesize button;

/*

    NSObject contains machinery to deal with the possibility
    that a class may be sent a selector to which it doesn't
    respond.

    As of iOS 4, forwardingTargetForSelector: can be used to
    nominate an alternative target for the selector quickly.

    In previous versions of iOS, or in iOS 4 if you don't
    respond to forwardingTargetForSelector:, you may take
    delivery of the problematic invocation and deal with it
    yourself.

    Dealing with the invocation costs more than providing
    a forwarding target for the selector, so its worth having
    both.

    If you're only targeting iOS 4 or above, you needn't
    keep the implementation of forwardInvocation: below.

    What we're doing is declaring a bunch of IBActions so
    that we can wire changes up to them in Interface Builder.
    By failing to implement them and providing the delegate
    as the thing to talk to for any selectors we don't know,
    we're allowing those wirings to be as though assigned
    to the delegate.

*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return delegate;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation setTarget:delegate];
    [anInvocation invoke];
}

@end

With interface:

@interface ReusableView : UIView 
{

    IBOutlet id delegate;

    IBOutlet UISegmentedControl *segmentedControl;
    IBOutlet UIButton *button;

}

@property (nonatomic, assign) id delegate;

@property (nonatomic, assign) UISegmentedControl *segmentedControl;
@property (nonatomic, assign) UIButton *button;

/*

    NB: we're not actually going to implement these. We're
    declaring them for the benefit of Interface Builder / Xcode 4.

    What we'll actually do is, any time we receive a selector
    we don't implement, hand it off to the delegate. So it's a
    quick way of avoiding writing any glue code to pass messages
    from this little reusable view to its delegate.

    A better alternative could define a formal protocol that
    forwards both the changed control and self from the
    reusable view to its delegate. But that's obvious and
    verbose, so has been omitted for the purposes of example.

    The implementation as stands will generate compiler warnings,
    but such is life. To get rid of the warnings, comment out
    the two following lines, but only AFTER you've wired
    everything up in Interface Builder / Xcode 4. They're left
    uncommented here to help draw attention to the point about
    selector/invocation forwarding that you'll see in the
    @implementation.

    !!!!!!!!!!!!!!!

            HENCE:

            delegates MUST implement the following methods.

    !!!!!!!!!!!!!!!

    We could work around that by checking at runtime whether
    the actual delegate implements them and forwarding to
    a dummy object that implements them to do nothing otherwise,
    but that's slightly beyond the point of the example.

*/
- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;

@end

Net effect is that if a view controller has a UIView of type ReusableView within a XIB, it gets the contents of ReusableVew.xib inserted at runtime. If it wires itself up as the delegate of ReusableView within Interface Builder / Xcode 4 and implements:

- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;

Then it gets the messages from the embedded views.

This is achieved very simply and very neatly in Objective-C by using NSObject's inherent ability to forward selectors (as of iOS 4) or invocations (in earlier versions, at a greater cost) that it doesn't implement rather than allow an exception to occur.


Yes, it's possible. Just as you could have (for instance) multiple UIViewControllers in your project, each with a UIImageView as a view, you can do the same with your own subclasses of UIView.

0

精彩评论

暂无评论...
验证码 换一张
取 消