Using interface builder to Create Loabable Views

2019-08-08 16:10发布

问题:

New Method of Getting Items out of Nib Files.

Example in answer below.

This new Answer is the latest change in this project and Question

I want to create a UIView subclass and I want to create an Interface builder file for the UIView. How would I connect the files owner to itself. I want to be able to Design the view and have it create the connections but I dont have a need for a view controller.

Other than loading the nib into an array can I use the nib file as a connector to the view itself ?

I hope i am Asking this properly. I will elaborate tho.

Creating a UIViewController I am given the choice to create an xib file for it. At which time i can add the UIViews to the xib and those connections would be made. However the files owner does not appear to have a UI Editor associated to it so I want to create a UIView that will become a sub view in another view. I dont really want to create it with code because Its a pain to layout the items manually. So I want to use the same construct to create a small uiview to put into another

EDIT:

I Edited this again when I found another even better way to load nib classes. I figured some stack overflow users would want to see this.

I wanted to find an elegant way to load a UIView from a nib. and with some info from stack overflow user PeyloW I have managed to do just that.

I am going to post my results as the information will lead to a solution but this solution feels exceptionally elegant to me.

  • First of all, I created a Xib File named "TableViewCells.xib"
    (Name Matches the Class Name therefore [[self class] description] == "TableViewCells")
  • Next I created the Classes that I am using for the views in the Nib. Classes dont matter, Just Make sure the "Custom Class" is selected for the Class Objects in interface builder
  • Finally I created the Class "TableViewCells" to match the xib filename

My new Header File.
(Each of the header files imported associate to the items in the single xib file.)

#import <Foundation/Foundation.h>
#import "TitleCell.h"
#import "ToggleCell.h"
#import "ButtonCell.h"
#import "TextCell.h"

@interface TableViewCells : NSObject {
    UINib *uiNibHolder;
}

@property (nonatomic, readonly) NSArray *nibArray;

// These are the classes that will be loadable.
- (TitleCell*) titleCell;
- (ToggleCell*) toggleCell;
- (ButtonCell*) buttonCell;
- (TextCell*) textCell;
- (id) loadViewByClass: (id) viewClass;
@end

and the Class file.

loadViewByClass: is the method that finds the item in the nib file.
the convenience accessors have the correct type for the class objects to be instantiated into.
NOTE: I would Not Recommend loading a Bunch of Views into this, the more views you load the more you have to create when you load the nib file.

#import "TableViewCells.h"

@implementation TableViewCells

@synthesize nibArray;

- (void) unloadProperties{
    [uiNibHolder release];
}
- (NSArray *)nibArray{
    return [uiNibHolder instantiateWithOwner:nil options:nil];
}
- (id) init{
    if ((self = [super init]))
    {
        // Im using the class name for the Xib file. 
        // This means it will look in TableViewCells.xib
        // You can change the class name, or pass a Nib name to this class 
        uiNibHolder = [[UINib nibWithNibName:[[self class] description] bundle: [NSBundle mainBundle]] retain];
    }
    return self;
}
- (TitleCell *)titleCell{
    return [self loadViewByClass:[TitleCell class]];
}
- (ToggleCell *)toggleCell{
    return [self loadViewByClass:[ToggleCell class]];
}
- (ButtonCell *)buttonCell{
    return [self loadViewByClass:[ButtonCell class]];
}
- (TextCell *)textCell{
    return [self loadViewByClass:[TextCell class]];
}
- (id)loadViewByClass:(Class)viewClass{
    for (id item in self.nibArray) 
    {
        if ([item isKindOfClass:viewClass])
        {
            return item;
        }
    }
    return nil;
}

- (void)dealloc{
    [self performSelector:@selector(unloadProperties)];
    [super dealloc];
}

@end

Being able to load by class is incredibly nice. And if the class is not in the xib file it will return nil.

This could be adapted to use multiple nib files depending on the type of views you were loading. Using a lazy load property technique you could load those nibs only when needed leaving the memory footprint small and still allowing you to get the classes loaded through one convenient class loader.

As a demonstration I used a TableView to load a handfull of views

@synthesize tableViewCellLoader = _tableViewCellLoader;
- (TableViewCells *)tableViewCellLoader{
    if (_tableViewCellLoader == nil){
        _tableViewCellLoader = [[TableViewCells alloc] init];
    }
    return _tableViewCellLoader;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    switch (indexPath.row) {
        case 0:
            return self.tableViewCellLoader.toggleCell;
            break;
        case 1: 
            return self.tableViewCellLoader.textCell;
            break;
        case 2:
            return self.tableViewCellLoader.titleCell;
            break;
        case 3:
            return self.tableViewCellLoader.buttonCell;
            break;
    }
    return nil;
}

As you can see I lazy loaded my TableViewCellLoader and if I wanted to I could make the TableViewCells class lazy load UINib objects only when their classes were called.

I love convenience in code.

And Still, Thanks again PeyloW.

回答1:

Files Owner is only a proxy object, it is not required to be specified at run-time, but will always be visible at design-time in Interface Builder.

You may ignore hooking anything up to Files Owner if you do not need it. The at run-time load the NIB-file using something like this:

NSArray* rootObject = [[NSBundle mainBundle] loadNibNamed:@"MyNib" 
                                                    owner:nil 
                                                  options:nil];

What you pass for the owner argument is what passes as the Files Owner proxy when loading the nib, nil works just fine.

The rootObject array contains all root level objects, but no proxies. Do note that the array is autoreleased, so if you need the loaded objects to stay around you must retain the array, or just the particular elements you are interested in.

Not that using loadNibNamed:owner:options: is IO bound. If performance is needed then you should use an instance of UINib to cache the NIB file in memory and instantiate objects from it.

UINib* myNib = [[UINib nibWithNibName:@"MyNib" 
                               bundle:[NSBundle mainBundle]] retain];
// And then instantiate using:
NSArray* rootObject = [myNib instantiateWithOwner:nil
                                          options:nil];


回答2:

You can do this, but there is no reason you can't create IBOutlets in your UIView and connect directly to them. So in the xib file you would have a root UIView, make sure you change the class to your custom class. Then you can can set the outlets directly onto the view, you are not required to use the file owner.



回答3:

Newest Answer by me

After much time and playing with this possibility I have tried a number of scenarios and came to the conclusion that the best possible scenario is to create the UINib for each request and get the object in question. A one time pass gives the best results. If you have an object you plan to make very often it is best to put it into its own nib, rather than in a nib with multiple different classes as each one will be created each time the UINib is initialized.

Here is the class that I use to accomplish this task.

LSObjectLoader.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface LSObjectLoader : NSObject {
    UINib *nibHolder;
    NSString *currentNibName;
    NSArray *nibArray;
}

- (id) loadViewFromNib: (NSString*) nibName ofClassType:(Class) classType;

@end

LSObjectLoader.m

//
//  LSObjectLoader.m
//  APMobile
//
//  Created by jason.mckinley on 2/8/12.
//  Copyright (c) 2012 LoKi-Systems. All rights reserved.
//

#import "LSObjectLoader.h"

@implementation LSObjectLoader

- (id) loadViewFromNib: (NSString*) nibName ofClassType:(Class) classType
{
    if (![currentNibName isEqualToString:nibName])
    {
        currentNibName = [nibName copy];
        nibHolder = [UINib nibWithNibName:currentNibName bundle:[NSBundle mainBundle]];
    }
    nibArray = [nibHolder instantiateWithOwner:nil options:nil];

    for (id item in nibArray)
    {
        if ([item isKindOfClass:classType])
        {
            return item;
        }
    }

    return nil;
}

@end

Finally a couple ways to implement this.

Method 1 Loading Directly.

LSObjectLoader *oLoader = [[LSObjectLoader alloc] init];
MyClass *thisInstance = [oLoader loadViewFromNib:@"TableViewCells"
                                     ofClassType:[MyClass class]];

Method 2 Class loading itself

Nib File "TableViewCells.xib" (Contains a "MyChatCell" class object)

Class File "MyChatCell.h/MyChatCell.m"

Header

#import <UIKit/UIKit.h>

@interface MyChatCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *name;
@property (weak, nonatomic) IBOutlet UILabel *message;
@property (weak, nonatomic) IBOutlet UIImageView *image;

+ (MyChatCell*) newChatCell;

@end

Implementation

#import "MyChatCell.h"
#import "LSObjectLoader.h"

@implementation MyChatCell

+ (LSObjectLoader*) nibLoader{
    static LSObjectLoader *_objectLoader;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _objectLoader = [[LSObjectLoader alloc] init];
    });
    return _objectLoader;
}


+ (MyChatCell *)newChatCell{
    return [[self nibLoader] loadViewFromNib:@"TableViewCells" ofClassType:[self class]];
}

- (id)initWithCoder:(NSCoder *)aDecoder{
    self = [super initWithCoder:aDecoder];
    if (self){
        // This is the init code for nib loading
    }
    return self;
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

@end

Method 3 Custom Loader Class

Nib File "MyChatCells.xib" (Contains "MeChat" and "ThemChat" class objects)

Class File "MyChatCells.h/MyChatCells.m"

Header File

#import <UIKit/UIKit.h>
#import "MeChat.h"
#import "ThemChat.h"

@interface MyChatCells : NSObject

+ (MeChat*) newMeChat;
+ (ThemChat*) newThemChat;

@end

Implementation File

#import "MyChatCells.h"

@implementation MyChatCells

+ (LSObjectLoader*) nibLoader{
    static LSObjectLoader *_objectLoader;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _objectLoader = [[LSObjectLoader alloc] init];
    });
    return _objectLoader;
}

+ (MeChat*)newMeChat{
    return [[self nibLoader] loadViewFromNib:NSStringFromClass([self class])
                                 ofClassType:[MeChat class]];
}

+ (ThemChat*)newThemChat{
    return [[self nibLoader] loadViewFromNib:NSStringFromClass([self class])
                                 ofClassType:[ThemChat class]];
}

@end