Loading a nib inside a nib

I am new to interface builder and I would like to have a screen that contains a 3x3 UIView grid, each containing a UIImageView and 4 UILabels.

My logic is probably wrong, but the way I am trying to achieve this was as follows:

  • Create UIView Nib file MyUIView.nib and layout in IB labeled imageView and 4
  • Create a UIView subclass called MyUIView.m that contains 1 IBOutlet UIImageView and 4 IBUutlet UILabels. Bind MyUIView.nib and MyUIView.m to the owner of the files and connect them.
  • Then create another nib MyGridViewController.nib that has 9 MyUIViews in it, laid out in a 3x3 grid.
  • Create a UIViewController that has 9 IBOutlets MyUIViews and connect them through the Interface Builder.

Is it possible to load a thread into other graphics graphically from the InterfaceBuilder interface, if so how should I do it? Do I drag the "standard" UIView onto the canvas and then change the class to MyUIView?

Or do I need to do it all programmatically in MyGridViewController.m with something like:

for (int i=0; i<9; i++)
{
    NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"MyUIView" owner:self options:nil];
   [myViewArray addObject:[ nibViews objectAtIndex: 1]];
}

      

The only other way I have worked with is to have one Nib and put 9 UIImageViews and 36 UILabels, but this is obviously a pain when I want to change something since I need to update each of the 3x3 "cells". I thought it would be easier to change it in one file and all 9 will be updated.

+2


source to share


6 answers


You cannot do this in Interface Builder.

What I'll probably do is make a custom view like MyGridItem, which loads MyUIView.nib as a sub-request when it wakes up, and then uses 9 of them in MyGridView.nib.

Be careful with awakeFromNib, as it can be called twice if the view is due to the loading of two different pens (for example, if MyGridItem is the owner when MyGridView.nib is loaded, then MyGridItem awakeFromNib will be called once, when it is loaded as part of loading MyUIView.nib and one time when it loads MyGridView.nib.

Also, since you are loading the 9 9 times, you may want to cache the size once using



NSNib* theNib = [[NSNib alloc] initWithNibNamed:@"MyGridItem" bundle:nil];

      

load it with

if ( [theNib instantiateNibWithOwner:self topLevelObjects:NULL] ) {

      

You can then release it after loading all nine objects.

+3


source


"Yes, you (almost) can."

I do this in my projects using Interface Builder.

The only drawback is that you see a white area for representing "nested threads" in Interface Builder. Let's say that at the same time (I expect Apple to add this feature to Xcode), the solution presented here is acceptable.

Read the following first: https://blog.compeople.eu/apps/?p=142

Then if you are doing ARC follow these instructions and grab my included UIVIew + Util category here.

For ARC, you have to enable this "self" control. ( https://blog.compeople.eu/apps/?p=142 state that it is not needed, but it does. If you don’t, you will get multiple messages sent to the freed instance,)

To achieve this in an ARC project add the '-fno-objc-arc' flags compiler option to your. Then do NO-ARC coding in that file (e.g. noalloc setting noils, calling super dealloc, etc.)

In addition, the client nib view controller must use a strong property to store the instance returned by awakeFromNib. In the case of my example code, customView is referenced like this:


@property ( strong , non-atomic) IBOutlet CustomView * customView;




Finally, I added some other improvements to property handling and nib loading using copyUIPropertiesTo: and loadNibNamed defined in my UIView + Util category .

So awakeAfterUsingCoder: the code is now

#import "UIView+Util.h"
...
- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder
{
    // are we loading an empty "placeholder" or the real thing?
    BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);

    if (theThingThatGotLoadedWasJustAPlaceholder)
    {
        CustomView* customView = (id) [CustomView loadInstanceFromNib];
        // copy all UI properties from self to new view!
        // if not, property that were set using Interface buider are lost!
        [self copyUIPropertiesTo:customView];

        [self release];
        // need retain to avoid deallocation
        self = [customView retain];
    }
    return self;
}

      

UIView + Util category code

@interface UIView (Util)
   +(UIView*) loadInstanceFromNib;
   -(void) copyUIPropertiesTo:(UIView *)view;
@end

      

along with its implementation

#import "UIView+Util.h"
#import "Log.h"

@implementation UIView (Util)

+(UIView*) 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; 
}

-(void) copyUIPropertiesTo:(UIView *)view
{
    // reflection did not work to get those lists, so I hardcoded them
    // any suggestions are welcome here

    NSArray *properties =
    [NSArray arrayWithObjects: @"frame",@"bounds", @"center", @"transform", @"contentScaleFactor", @"multipleTouchEnabled", @"exclusiveTouch", @"autoresizesSubviews", @"autoresizingMask", @"clipsToBounds", @"backgroundColor", @"alpha", @"opaque", @"clearsContextBeforeDrawing", @"hidden", @"contentMode", @"contentStretch", nil];

    // some getters have 'is' prefix
    NSArray *getters =
    [NSArray arrayWithObjects: @"frame", @"bounds", @"center", @"transform", @"contentScaleFactor", @"isMultipleTouchEnabled", @"isExclusiveTouch", @"autoresizesSubviews", @"autoresizingMask", @"clipsToBounds", @"backgroundColor", @"alpha", @"isOpaque", @"clearsContextBeforeDrawing", @"isHidden", @"contentMode", @"contentStretch", nil];

    for (int i=0; i<[properties count]; i++)
    {
        NSString * propertyName = [properties objectAtIndex:i];
        NSString * getter = [getters objectAtIndex:i];

        SEL getPropertySelector = NSSelectorFromString(getter);

        NSString *setterSelectorName =
            [propertyName stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[propertyName substringToIndex:1] capitalizedString]];

        setterSelectorName = [NSString stringWithFormat:@"set%@:", setterSelectorName];

        SEL setPropertySelector = NSSelectorFromString(setterSelectorName);

        if ([self respondsToSelector:getPropertySelector] && [view respondsToSelector:setPropertySelector])
        {
            NSObject * propertyValue = [self valueForKey:propertyName];

            [view setValue:propertyValue forKey:propertyName];
        }
    }    
}

      

Tadaaaa :-)

Credits are sent to https://stackoverflow.com/users/45018/yang for an initial solution. I just improved it.

+2


source


If you create your view with 1 image and 4 labels in anything (I'll call this view "grid cell"), you can create another thread for your grid and drag to 9 instances of the grid cell, 9 instances will only show as placeholders (empty views) in IB, but they will work when the application starts.

This article explains how to do this. Check out the Reusing Subviews section. And since you're creating a grid with 9 identical cells, it might make sense to use an AQGridView - see the Reusable AQGridViewCell section.

+1


source


AFAIK, there is no way to load the NIB within the NIB. What I would do, in your case, is add the UILabels programmatically to MyUIView.

0


source


Here's a slightly more dynamic way to do something like this.

I really didn't want to tighten the umbilical cords and sift through their objects, wherever I wanted to use them in the code felt messy. Also, I wanted to be able to add them to the interface constructor.

1) Create your own UIView subclass (call it myView) 2) Create your pin and name it myViewNIB

Add these two methods (it would be smarter to have them in the UIView superclass and subclass)

- (UINib *)nib {
    NSBundle *classBundle = [NSBundle bundleForClass:[self class]];
    return [UINib nibWithNibName:[self nibName] bundle:classBundle];
}

- (NSString *)nibName {
    NSString *className = NSStringFromClass([self class]);
    return [NSString stringWithFormat:@"%@NIB", className];
}

      

The so called magic is the last line where it returns the nibName, adding NIB to the current class.

Then for your init method, which is initWithCoder, since you want to use it in the interface builder (you can make it more robust so that it can be used programmatically by setting initWithFrame as well):

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        // Initialization code
        NSArray *nibObjects = [[self nib] instantiateWithOwner:nil options:nil];

        UIView *view = [nibObjects objectAtIndex:0];
        [self addSubview:view];

    }
    return self;
}

      

So it requires the first (and probably only) myViewNIB to be a view. What it won't give you is a way, at a minimum, to program within the tags, like labels. Without getting hung up on views and tagging, I'm not sure how you would go about this.

Anyway. At this point, you can drag the new UIView into IB and set the class to myView. myView will automatically search for our associated myViewNIB.xib and add its view as a subcategory.

There seems to be a better way to do this.

0


source


What I did was:

1) For the owner view, I put the subview as an empty view with the custom view class name as a placeholder. This will create a custom view, but none of its subzones or connections will be made as they are defined in the custom nib view.

2) Then I manually load a new copy of the custom view using my nib and replace the custom placeholder view in the parent.

Using:

    NSView *newView = (Code to load the view using a nib.)
    newView.frame = origView.frame;
    NSView *superView = origView.superview;
    [superView replaceSubview:origView with:newView];

      

Ugh!

0


source







All Articles