0

I have a Custom View I created that should appear in several View Controllers in the App.

So... I've created a subview of UIView with xib, h and m files for it (and set the View in the xib file with the Class), added my UIs to the view xib file and connected them as IBOutlets:

enter image description here

enter image description here

enter image description here

Then... I added 2 UIViews to a View Controller, set their Class to MYCustomView and connected IBOutlets of that type to the VC:

enter image description here

enter image description here

enter image description here

And... it doesn't work...

Now... I'm familiar with the technique of loading the xib using the NSBundle loadNibNamed:owner:options: method.

But... I would really like to be able to set the instances of MYCustomView in IB directly (constraints, sizing, etc...).

Is there another way to do this? Or should I just create 'placeholder' UIViews and add my instances of MYCustomView as their subviews?

Black Frog
  • 11,595
  • 1
  • 35
  • 66
Ohad Regev
  • 5,641
  • 12
  • 60
  • 84

3 Answers3

2

There is some UIView category that I always use for custom views, you can use it yourself with few easy steps:

Click command + N to add a new Objective-C category. In the header file add those lines:

// This category let the UIView that using it to load a XIB with the same name as the UIView.h & .m files easily.
// See: MyContentWaitingSong custom view for reference.
@interface UIView (NibLoading)

+ (id)loadInstanceFromNib;

@end

Than in your .m file add those lines:

// This category let the UIView that using it to load a XIB with the same name as the UIView.h & .m files easily.
// See: MyContentWaitingSong custom view for reference.
@implementation UIView (NibLoading)

+ (id)loadInstanceFromNib
{
    UIView *result = nil;

    NSArray* elements = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil];

    for (id anObject in elements) {
        if ([anObject isKindOfClass:[self class]]) {
            result = anObject;
            break;
        }
    }

    return result;
}

@end

Than you can use it in your custom view class, just import the category and implement an init method like this one:

- (id)init
{
    self = [YourCustomClassName loadInstanceFromNib];
    if (self)
    {
        //    Your initial implementation
    }
    return self;
}

Than your implementation should look like that:

YourCustomClassName *someName = [[YourCustomClassName alloc] init];
someName.frame = frame;
[self.view addSubView:someName];

Good luck.

nathan_ni
  • 21
  • 2
  • 1
    Nice answer, I use something like this (but a bit more complex). The only thing is that that's the answer to a different question. – MANIAK_dobrii Oct 30 '14 at 14:39
1

No, you a asking about referencing nibs from other nibs. There are ways to achieve this with some awakeAfterUsingCoder: hacks or something like that. Usually you check if view has no subviews and load view from nib then or create some IB-dummy class that replaces itself with target class after it get's loaded. All this techniques have issues, so I'd recommend against them.

So I'd suggest loading it from code, nice catch would be to use view controller containment.

There's something really cool, appeared relatively not long ago: IBDesignable and IBInspectable, it had to appear at some point.

One example would be some temporary code I used while required framework was not yet ready (It's an UIButton dummy IB subclass):

//
// That's just an example, do not use it in production code
//
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
    UIButton *buttonMain = // create button, which will replace button of dummy class from nib
    buttonMain.translatesAutoresizingMaskIntoConstraints = NO;

    // copy internal constraints
    [self copyInternalConstraintsToView:buttonMain];

    // set titles
    NSString *title = [self titleForState:UIControlStateNormal];
    [buttonMain setTitle:title forState:UIControlStateNormal];

    title = [self titleForState:UIControlStateHighlighted];
    [buttonMain setTitle:title forState:UIControlStateHighlighted];

    title = [self titleForState:UIControlStateDisabled];
    [buttonMain setTitle:title forState:UIControlStateDisabled];

    title = [self titleForState:UIControlStateSelected];
    [buttonMain setTitle:title forState:UIControlStateSelected];

    return buttonMain;
}

/**
 *  Used only in awakeAfterUsingCoder, as self has only internal constraints at that moment.
 *  If you'll try to use it somewhere else, behavior is undefined.
 *
 *  This method is straightforward and could not suit everybody's needs.
 *
 *  %Comments edited%
 *
 *  @param replacementView view to add constraints to
 */
- (void)copyInternalConstraintsToView:(UIView *)replacementView
{
    for (NSLayoutConstraint* constraint in self.constraints)
    {
        if([constraint isMemberOfClass:[NSLayoutConstraint class]])
        {
            id first = constraint.firstItem;
            id second = constraint.secondItem;
            id newFirst = first;
            id newSecond = second;

            BOOL match = NO;
            if (first == self)
            {
                newFirst = replacementView;
                match = YES;
            }
            if (second == self)
            {
                newSecond = replacementView;
                match = YES;
            }


            // constraints to subviews from recource are not supported
            if(newFirst != nil && newFirst != replacementView)
            {
                match = NO;
            }

            if(newSecond != nil && newSecond != replacementView)
            {
                match = NO;
            }

            if (match)
            {
                @try
                {
                    NSLayoutConstraint* newConstraint = nil;
                    newConstraint = [NSLayoutConstraint constraintWithItem:newFirst
                                                                 attribute:constraint.firstAttribute
                                                                 relatedBy:constraint.relation
                                                                    toItem:newSecond
                                                                 attribute:constraint.secondAttribute
                                                                multiplier:constraint.multiplier
                                                                  constant:constraint.constant];
                    newConstraint.shouldBeArchived = constraint.shouldBeArchived;
                    newConstraint.priority = constraint.priority;

                    [replacementView addConstraint:newConstraint];
                }
                @catch (NSException *exception)
                {
                    NSLog(@"Constraint exception: %@\nFor constraint: %@", exception, constraint);
                }
            }
        }
    }
}

Some links: Embedding custom-view Nibs in another Nib: Towards the holy grail, An Update on Nested Nib Loading

MANIAK_dobrii
  • 6,014
  • 3
  • 28
  • 55
1

Well I think there are two possible approaches for that:

  1. As you mentioned use the technique with loadNibNamed:owner:options
  2. If you are using Storyboards you can use so called container views. Just style your custom view within the storyboard, connect it by using a segue and you are almost done.

For the second approach you need to use a view controller to put in your custom view, but this should't be a problem at all. In my opinion it is even better to use a separate view controller to put in your view controller logic.

anka
  • 3,817
  • 1
  • 30
  • 36