Как загрузить пользовательские UITableViewCells из файлов Xib? - PullRequest
288 голосов
/ 12 февраля 2009

Вопрос прост: как загрузить пользовательские UITableViewCell из файлов Xib? Это позволяет вам использовать Interface Builder для проектирования ваших ячеек. Ответ очевидно не прост из-за проблем управления памятью. В этой теме упоминается проблема и предлагается решение, но она выпущена до выпуска NDA и не содержит кода. Вот длинная нить , в которой обсуждается проблема без однозначного ответа.

Вот код, который я использовал:

static NSString *CellIdentifier = @"MyCellIdentifier";

MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
    cell = (MyCell *)[nib objectAtIndex:0];
}

Чтобы использовать этот код, создайте MyCell.m / .h, новый подкласс UITableViewCell и добавьте IBOutlets для компонентов, которые вы хотите. Затем создайте новый файл «Пустой XIB». Откройте файл Xib в IB, добавьте объект UITableViewCell, установите для его идентификатора значение «MyCellIdentifier», установите для его класса значение MyCell и добавьте свои компоненты. Наконец, подключите IBOutlets к компонентам. Обратите внимание, что мы не установили владельца файла в IB.

Другие методы рекомендуют устанавливать владельца файла и предупреждать об утечках памяти, если Xib не загружается через дополнительный фабричный класс. Я проверил вышеизложенное в разделе «Инструменты / утечки» и не обнаружил утечек памяти.

Так каков канонический способ загрузки ячеек из Xibs? Мы устанавливаем владельца файла? Нужен ли нам завод? Если да, то как выглядит код фабрики? Если есть несколько решений, давайте выясним плюсы и минусы каждого из них ...

Ответы [ 23 ]

4 голосов
/ 14 декабря 2011

Перезагрузка NIB стоит дорого. Лучше загрузить его один раз, а затем создавать объекты, когда вам нужна ячейка. Обратите внимание, что вы можете добавить UIImageViews и т. Д. В перо, даже в несколько ячеек, используя этот метод (Apple «registerNIB» iOS5 допускает только один объект верхнего уровня - Ошибка 10580062 "iOS5 tableView registerNib: чрезмерно ограничительный"

Итак, мой код ниже - вы читаете в NIB один раз (при инициализации, как я, или в viewDidload - что угодно. С тех пор вы создаете экземпляр nib в объектах, а затем выбираете нужный). загрузка пера снова и снова.

static UINib *cellNib;

+ (void)initialize
{
    if(self == [ImageManager class]) {
        cellNib = [UINib nibWithNibName:@"ImageManagerCell" bundle:nil];
        assert(cellNib);
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellID = @"TheCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if(cell == nil) {
        NSArray *topLevelItems = [cellNib instantiateWithOwner:nil options:nil];
        NSUInteger idx = [topLevelItems indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
                            {
                                UITableViewCell *cell = (UITableViewCell *)obj;
                                return [cell isKindOfClass:[UITableViewCell class]] && [cell.reuseIdentifier isEqualToString:cellID];
                            } ];
        assert(idx != NSNotFound);
        cell = [topLevelItems objectAtIndex:idx];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"Howdie %d", indexPath.row];

    return cell;
}
4 голосов
/ 09 июля 2013

Проверьте это - http://eppz.eu/blog/custom-uitableview-cell/ - действительно удобный способ использования крошечного класса, который заканчивается одной строкой в ​​реализации контроллера:

-(UITableViewCell*)tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath
{
    return [TCItemCell cellForTableView:tableView
                          atIndexPath:indexPath
                      withModelSource:self];
}

enter image description here

3 голосов
/ 29 июня 2012

Правильный способ сделать это - создать реализацию подкласса UITableViewCell, заголовок и XIB. В XIB удалите все представления и просто добавьте ячейку таблицы. Установите класс как имя подкласса UITableViewCell. Для владельца файла сделайте его именем класса подкласса UITableViewController. Подключите владельца файла к ячейке, используя выход tableViewCell.

В заголовочном файле:

UITableViewCell *_tableViewCell;
@property (assign) IBOutlet UITableViewCell *tableViewCell;

В файле реализации:

@synthesize tableViewCell = _tableViewCell;

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *kCellIdentifier = @"reusableCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:kCellIdentifier owner:self options:nil];
        cell = _tableViewCell;
        self.tableViewCell = nil;
    }

    return cell;
}
3 голосов
/ 12 февраля 2009

Для этого я объявляю IBOutlet UITableViewCell *cell в вашем классе контроллера. Затем вызовите метод класса NSBundle loadNibNamed, который будет передавать UITableViewCell в ячейку, объявленную выше.

Для xib я создам пустой xib и добавлю объект UITableViewCell в IB, где он может быть настроен по мере необходимости. Этот вид затем подключается к ячейке IBOutlet в классе контроллера.

- (UITableViewCell *)tableView:(UITableView *)table
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"%@ loading RTEditableCell.xib", [self description] );

    static NSString *MyIdentifier = @"editableCellIdentifier";
    cell = [table dequeueReusableCellWithIdentifier:MyIdentifier];

    if(cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"RTEditableCell"
                                      owner:self
                                    options:nil];
    }

    return cell;
}

NSBundle дополнения loadNibNamed (вход в АЦП)

cocoawithlove.com статья, из которой я получил эту концепцию (пример приложения для телефонных номеров)

2 голосов
/ 07 сентября 2017
  1. Создайте свой собственный настроенный класс AbcViewCell подкласс из UITableViewCell (убедитесь, что имя файла класса и имя файла пера совпадают)

  2. Создать этот метод класса расширения.

    extension UITableViewCell {
        class func fromNib<T : UITableViewCell>() -> T {
            return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)?[0] as! T
        }
    }
    
  3. Используй это.

    let cell: AbcViewCell = UITableViewCell.fromNib()

2 голосов
/ 26 февраля 2014

Сначала импортируйте свой файл ячейки #import "CustomCell.h", а затем измените метод делегата, как указано ниже:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *simpleTableIdentifier = @"CustomCell";

CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
    cell = [nib objectAtIndex:0];

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}         

     return cell;
}
1 голос
/ 24 марта 2011

Вот мой метод для этого: Загрузка пользовательских UITableViewCells из файлов XIB ... Еще один метод

Идея заключается в создании подкласса SampleCell UITableViewCell со свойством IBOutlet UIView *content и свойством для каждого настраиваемого подпредставления, которое необходимо настроить из кода. Затем создать файл SampleCell.xib. В этом nib-файле измените владельца файла на SampleCell. Добавьте контент размером UIView в соответствии с вашими потребностями. Добавьте и настройте все необходимые подпредставления (метка, изображения, кнопки и т. Д.). Наконец, свяжите представление содержимого и подпредставления с владельцем файла.

1 голос
/ 12 октября 2018

In Swift 4.2 и Xcode 10

У меня есть три файла ячеек XIB

в ViewDidLoad зарегистрируйте ваши файлы XIB следующим образом ...

Это первый подход

tableView.register(UINib.init(nibName: "XIBCell", bundle: nil), forCellReuseIdentifier: "cell1")
tableView.register(UINib.init(nibName: "XIBCell2", bundle: nil), forCellReuseIdentifier: "cell2")
//tableView.register(UINib.init(nibName: "XIBCell3", bundle: nil), forCellReuseIdentifier: "cell3")

Второй подход - непосредственно зарегистрировать файлы XIB в cellForRowAt indexPath:

Это мои функции делегирования табличного представления

//MARK: - Tableview delegates
override func numberOfSections(in tableView: UITableView) -> Int {

    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return 6
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //This is first approach
    if indexPath.row == 0 {//Load first XIB cell
        let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell1") as! XIBCell
        return placeCell
    //Second approach
    } else if indexPath.row == 5 {//Load XIB cell3
        var cell = tableView.dequeueReusableCell(withIdentifier:"cell3") as? XIBCell3
        if cell == nil{
            let arrNib:Array = Bundle.main.loadNibNamed("XIBCell3",owner: self, options: nil)!
            cell = arrNib.first as? XIBCell3
        }

        //ADD action to XIB cell button
        cell?.btn.tag = indexPath.row//Add tag to button
        cell?.btn.addTarget(self, action: #selector(self.bookbtn1(_:)), for: .touchUpInside);//selector

        return cell!
    //This is first approach
    } else {//Load XIB cell2
        let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell2") as! XIBCell2

        return placeCell
    }

}
1 голос
/ 18 августа 2017

Вот универсальный подход для регистрации ячеек в UITableView:

protocol Reusable {
    static var reuseID: String { get }
}

extension Reusable {
    static var reuseID: String {
        return String(describing: self)
    }
}

extension UITableViewCell: Reusable { }

extension UITableView {

func register<T: UITableViewCell>(cellClass: T.Type = T.self) {
    let bundle = Bundle(for: cellClass.self)
    if bundle.path(forResource: cellClass.reuseID, ofType: "nib") != nil {
        let nib = UINib(nibName: cellClass.reuseID, bundle: bundle)
        register(nib, forCellReuseIdentifier: cellClass.reuseID)
    } else {
        register(cellClass.self, forCellReuseIdentifier: cellClass.reuseID)
    }
}

Пояснение:

  1. Reusable протокол генерирует идентификатор ячейки из имени класса. Обязательно соблюдайте соглашение: cell ID == class name == nib name.
  2. UITableViewCell соответствует протоколу Reusable.
  3. UITableView расширение абстрагирует разницу в регистрации ячеек через nib или класс.

Пример использования:

override func viewDidLoad() {
    super.viewDidLoad()
    let tableView = UITableView()
    let cellClasses: [UITableViewCell.Type] = [PostCell.self, ProfileCell.self, CommentCell.self]
    cellClasses.forEach(tableView.register)
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: PostCell.self.reuseID) as? PostCell
    ...
    return cell
}
0 голосов
/ 12 февраля 2009

Я не знаю, есть ли канонический путь, но вот мой метод:

  • Создание xib для ViewController
  • Установите для класса «Владелец файла» значение UIViewController
  • Удалить вид и добавить UITableViewCell
  • Установите класс вашего UITableViewCell в свой пользовательский класс
  • Установите идентификатор вашего UITableViewCell
  • Установите выход вашего представления контроллера представления на UITableViewCell

И используйте этот код:

MyCustomViewCell *cell = (MyCustomViewCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
  UIViewController* c = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil];
  cell = (MyCustomViewCell *)c.view;
  [c release];
}

В вашем примере, используя

[nib objectAtIndex:0]

может сломаться, если Apple изменит порядок элементов в xib.

...