Использование Interface Builder для создания загружаемых представлений

#iphone #uiview #interface-builder

#iPhone #uiview #interface-builder

Вопрос:

Новый метод извлечения элементов из файлов Nib.

Пример в ответе ниже.

Этот новый ответ является последним изменением в этом проекте и вопросе

Я хочу создать подкласс UIView и я хочу создать файл Interface builder для UIView. Как бы я подключил владельца файлов к самому себе. Я хочу иметь возможность создавать представление и создавать соединения, но мне не нужен контроллер представления.

Могу ли я использовать файл nib в качестве соединителя с самим представлением, помимо загрузки в массив?

Я надеюсь, что задаю это правильно. Я подробно остановлюсь на этом.

Создание UIViewController Мне предоставляется выбор создать для него файл xib. В это время я могу добавить UIViews в xib, и эти соединения будут установлены. Однако у владельца файлов, похоже, нет связанного с ним редактора пользовательского интерфейса, поэтому я хочу создать UIView, который станет вспомогательным представлением в другом представлении. Я действительно не хочу создавать это с помощью кода, потому что вручную размещать элементы довольно сложно. Итак, я хочу использовать ту же конструкцию для создания небольшого uiview для размещения в другом

Редактировать:

Я отредактировал это снова, когда нашел другой, еще лучший способ загрузки классов nib. Я подумал, что некоторые пользователи stack overflow захотят это увидеть.

Я хотел найти элегантный способ загрузки пользовательского интерфейса из кончика. и с некоторой информацией от пользователя stack overflow PeyloW мне удалось сделать именно это.

Я собираюсь опубликовать свои результаты, поскольку информация приведет к решению, но это решение кажется мне исключительно элегантным.

  • Прежде всего, я создал файл Xib с именем «TableViewCells.xib»
    (Имя совпадает с именем класса, поэтому [[описание собственного класса]] == «TableViewCells»)
  • Затем я создал классы, которые я использую для представлений в Nib. Классы не имеют значения, просто убедитесь, что для объектов класса в interface Builder выбран «Пользовательский класс»
  • Наконец, я создал класс «TableViewCells», соответствующий имени файла xib

Мой новый заголовочный файл.
(Каждый из импортированных файлов заголовков связан с элементами в одном файле xib.)

 #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
  

и файл класса.

loadViewByClass: это метод, который находит элемент в файле nib.
средства доступа для удобства имеют правильный тип для объектов класса, в которые будут созданы экземпляры.
ПРИМЕЧАНИЕ: Я бы не рекомендовал загружать в это кучу представлений, чем больше представлений вы загружаете, тем больше вам нужно создать при загрузке файла nib.

 #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
  

Возможность загрузки по классам невероятно приятна. И если класс отсутствует в файле xib, он вернет nil.

Это может быть адаптировано для использования нескольких файлов nib в зависимости от типа загружаемых вами представлений. Используя метод отложенной загрузки свойств, вы могли бы загружать эти nibs только при необходимости, оставляя небольшой объем памяти и по-прежнему позволяя загружать классы через один удобный загрузчик классов.

В качестве демонстрации я использовал TableView для загрузки нескольких представлений

 @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;
}
  

Как вы можете видеть, я лениво загрузил свой TableViewCellLoader, и если бы я захотел, я мог бы заставить класс TableViewCells лениво загружать объекты UINib только при вызове их классов.

Я люблю удобство в коде.

И все же, еще раз спасибо Пейлоу.

Ответ №1:

Владелец файлов — это всего лишь прокси-объект, его не обязательно указывать во время выполнения, но он всегда будет виден во время разработки в Interface Builder.

Вы можете игнорировать подключение чего-либо к владельцу файлов, если вам это не нужно. Во время выполнения загружается файл NIB, используя что-то вроде этого:

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

То, что вы передаете в качестве owner аргумента, передается как прокси владельца файлов при загрузке nib, nil работает просто отлично.

rootObject Массив содержит все объекты корневого уровня, но не прокси. Обратите внимание, что массив автоматически освобождается, поэтому, если вам нужно, чтобы загруженные объекты оставались рядом, вы должны сохранить массив или только конкретные элементы, которые вас интересуют.

Не то, чтобы использование loadNibNamed:owner:options: было связано с вводом-выводом. Если требуется производительность, то вам следует использовать экземпляр UINib для кэширования файла NIB в памяти и создания из него экземпляров объектов.

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

Комментарии:

1. Это грубый процесс. Я пытаюсь найти более элегантный способ загрузки кончика. Поскольку этот процесс загружает массив объектов, и мне пришлось бы получить объект по индексу. Это просто нечистый процесс. Поскольку UIViewController загружает файл с тем же именем и присоединяет объект owner к этому инициализированному классу. Я хотел попробовать сделать то же самое. Но делайте это только с помощью UIView.

2. @Jason — Это не грубый, это просто инструмент, созданный и оптимизированный для загрузки ресурсов с контроллера. Если вам нужно что-то еще, возможно, это неподходящий инструмент, или вам нужно реализовать какой-нибудь удобный метод, например: -[NSBundle loadViewWithTag:fromNibNamed:] , который загружает перо и возвращает желаемый вид. Вы можете расширить любой класс, используя категории, чтобы лучше соответствовать вашим потребностям и дизайну.

3. как бы помог тег? могу ли я назвать представления в виде кончика? если это так, я не был в курсе …. это было бы огромным плюсом.

4. @Jason — UIView имеет свойство с именем tag это простое целое число и предназначено для нас, разработчиков, для идентификации и поиска представлений. Наиболее заметно при использовании иерархии на экране -[UIView viewWithTag:] . Но вы также можете определить это в Interface Builder и использовать его для поиска правильного экземпляра в возвращаемом массиве при загрузке файла NIB. UIKit никогда не будет использовать или изменять tag свойство, это исключительно для вас.

5. Таким образом, мне все равно пришлось бы извлекать массив, затем перебирать массив, чтобы найти представление (по тегу), а затем использовать его. Это правильно.

Ответ №2:

Вы можете это сделать, но нет никаких причин, по которым вы не можете создавать IBOutlets в своем UIView и подключаться к ним напрямую. Итак, в файле xib у вас должен быть root UIView, убедитесь, что вы изменили класс на свой пользовательский класс. Затем вы можете устанавливать выходы непосредственно в представлении, вам не требуется использовать владельца файла.

Комментарии:

1. как создание выходов будет связано с классом. Я пытаюсь избежать необходимости явно загружать перо и извлекать представление из созданного массива. Поскольку UIViewController загружает перо с тем же именем. Есть ли способ заставить UIView делать то же самое?

Ответ №3:

Самый новый ответ от меня

После долгого использования этой возможности я перепробовал несколько сценариев и пришел к выводу, что наилучшим возможным сценарием является создание UINib для каждого запроса и получение соответствующего объекта. Наилучшие результаты дает одноразовый проход. Если у вас есть объект, который вы планируете создавать очень часто, то лучше поместить его в отдельный раздел, а не в раздел с несколькими разными классами, поскольку каждый из них будет создаваться при каждой инициализации UINib.

Вот класс, который я использую для выполнения этой задачи.

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
  

Наконец, пара способов реализовать это.

Способ 1 Загрузка напрямую.

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

Метод 2 Загрузка класса самостоятельно

Файл Nib «TableViewCells.xib» (Содержит объект класса «MyChatCell»)

Файл класса «MyChatCell.h / MyChatCell.m»

Заголовок

 #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
  

Реализация

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

@implementation MyChatCell

  (LSObjectLoader*) nibLoader{
    static LSObjectLoader *_objectLoader;
    static dispatch_once_t onceToken;
    dispatch_once(amp;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
  

Метод 3 Пользовательский класс загрузчика

Файл Nib «MyChatCells.xib» (Содержит объекты классов «MeChat» и «ThemChat»)

Файл класса «MyChatCells.h/ MyChatCells.m»

Заголовочный файл

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

@interface MyChatCells : NSObject

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

@end
  

Файл реализации

 #import "MyChatCells.h"

@implementation MyChatCells

  (LSObjectLoader*) nibLoader{
    static LSObjectLoader *_objectLoader;
    static dispatch_once_t onceToken;
    dispatch_once(amp;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