В раскадровке, как сделать пользовательскую ячейку для использования с несколькими контроллерами? - PullRequest
216 голосов
/ 12 февраля 2012

Я пытаюсь использовать раскадровки в приложении, над которым я работаю.В приложении есть Списки и Пользователи , каждый из которых содержит коллекцию других (члены списка, списки, принадлежащие пользователю).Итак, соответственно, у меня есть ListCell и UserCell классы.Цель состоит в том, чтобы их можно было повторно использовать во всем приложении (т. Е. В любом из моих контроллеров таблиц).

Вот где я столкнулся с проблемой.

Как создать настраиваемую ячейку табличного представления в раскадровке, которую можно повторно использовать в любом контроллере представления?

Вот конкретные вещи, которые я пробовал такfar.

  • В Controller # 1 добавили ячейку прототипа, задали класс для моего подкласса UITableViewCell, задали идентификатор повторного использования, добавили метки и подключили их к выходам класса.В Controller # 2 добавили пустую ячейку прототипа, установили для нее тот же класс и повторно использовали идентификатор, как и раньше.Когда он запускается, метки никогда не появляются, когда ячейки отображаются в контроллере # 2.Прекрасно работает в контроллере № 1.

  • Разработан каждый тип ячейки в отдельном NIB и подключен к соответствующему классу ячейкиВ раскадровку добавили пустую ячейку прототипа и задали ее класс и идентификатор повторного использования для ссылки на мой класс ячейки.В методах viewDidLoad контроллеров регистрируются эти файлы NIB для идентификатора повторного использования.Когда показано, ячейки в обоих контроллерах были пустыми, как в прототипе.

  • Сохраняли прототипы в обоих контроллерах пустыми и устанавливали класс и повторно использовали id для моего класса ячеек.Пользовательский интерфейс ячеек построен полностью в коде.Ячейки отлично работают во всех контроллерах.

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

В конце концов, я хочу две вещи: подключить на основе таблицыпотоки в раскадровке и определяют расположение ячеек визуально, а не в коде.Я пока не понимаю, как их получить.

Ответы [ 6 ]

204 голосов
/ 12 февраля 2012

Насколько я понимаю, вы хотите:

  1. Создать ячейку в IB, которую можно использовать в нескольких сценах раскадровки.
  2. Настроить уникальные сегменты раскадровки из этой ячейки, в зависимости отна сцене находится ячейка.

К сожалению, в настоящее время нет способа сделать это.Чтобы понять, почему ваши предыдущие попытки не сработали, вам нужно больше узнать о том, как работают раскадровки и ячейки прототипа табличного представления.(Если вас не волнует , почему эти другие попытки не сработали, не стесняйтесь уходить. У меня нет для вас волшебных обходных путей, кроме как предложить вам сообщить об ошибке.)

Раскадровка, по сути, не намного больше, чем набор файлов .xib.Когда вы загружаете контроллер табличного представления, у которого есть несколько ячеек-прототипов, из раскадровки, вот что происходит:

  • Каждая ячейка-прототип на самом деле является собственным встроенным мини-пером.Поэтому, когда контроллер табличного представления загружается, он проходит через все перья ячейки прототипа и вызывает -[UITableView registerNib:forCellReuseIdentifier:].
  • В табличном представлении запрашивается у контроллера ячейки.
  • Вы, вероятно, вызываете-[UITableView dequeueReusableCellWithIdentifier:]
  • Когда вы запрашиваете ячейку с заданным идентификатором повторного использования, она проверяет, зарегистрировано ли у нее перо.Если это так, он создает экземпляр этой ячейки.Это состоит из следующих шагов:

    1. Посмотрите на класс ячейки, как определено в кончике ячейки.Вызовите [[CellClass alloc] initWithCoder:].
    2. Метод -initWithCoder: проходит через него, добавляет подпредставления и устанавливает свойства, которые были определены в кончике.(IBOutlet s, вероятно, и здесь подключены, хотя я не проверял это; это может произойти в -awakeFromNib)
  • Вы настраиваете свою ячейку, однако выхочу.

Здесь важно отметить, что существует различие между классом ячейки и визуальным внешним видом ячейки,Вы можете создать две отдельные ячейки-прототипы одного и того же класса, но их подвиды выложены совершенно по-разному.На самом деле, если вы используете стили UITableViewCell по умолчанию, это именно то, что происходит.Например, стиль "Default" и стиль "Subtitle" представлены одним и тем же классом UITableViewCell.

Это важно : class ячейки не имеет взаимно-однозначной корреляции с определенной иерархией представления .Иерархия представления полностью определяется тем, что находится в ячейке прототипа, которая была зарегистрирована этим конкретным контроллером.

Также обратите внимание, что идентификатор повторного использования ячейки не был зарегистрирован в некотором глобальном диспансере ячейки.Идентификатор повторного использования используется только в контексте одного UITableView экземпляра.


Учитывая эту информацию, давайте посмотрим, что произошло в ваших вышеупомянутых попытках.

ВКонтроллер # 1, добавил ячейку прототипа, установил класс в мой подкласс UITableViewCell, установил идентификатор повторного использования, добавил метки и подключил их к выходам класса.В Controller # 2 добавили пустую ячейку прототипа, установили для нее тот же класс и повторно использовали идентификатор, как и раньше.Когда он запускается, метки никогда не появляются, когда ячейки отображаются в контроллере # 2.Работает нормально в Controller # 1.

Это ожидается.Хотя обе ячейки имели один и тот же класс, иерархия представлений, которая была передана в ячейку контроллера № 2, была полностью лишена подпредставлений.Таким образом, вы получили пустую ячейку, которая является именно тем, что вы положили в прототип.

Разработан каждый тип ячейки в отдельном NIB и подключен к соответствующему классу ячейки.В раскадровку добавили пустую ячейку прототипа и задали ее класс и идентификатор повторного использования для ссылки на мой класс ячейки.В методах контроллеров viewDidLoad регистрировали эти файлы NIB для идентификатора повторного использования.Когда показано, ячейки в обоих контроллерах были пусты, как в прототипе.

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

Однако это решение было близко.Как вы заметили, вы можете просто программно позвонить -[UITableView registerNib:forCellReuseIdentifier:], передав UINib, содержащий ячейку, и вы вернетесь к той же самой ячейке.(Это не потому, что прототип «перекрывал» перо; вы просто не зарегистрировали перо в виде таблицы, поэтому оно все еще смотрело на перо, встроенное в раскадровку.) К сожалению, у этого подхода есть недостаток -нет никакого способа подключить сегменты раскадровки к ячейке в автономном перо.

Сохранять прототипы на обоих контроллерах пустыми и устанавливать класс и повторно использовать id для моего класса ячейки.Пользовательский интерфейс ячеек построен полностью в коде.Ячейки отлично работают во всех контроллерах.

Естественно.Надеюсь, это неудивительно.


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

58 голосов
/ 24 апреля 2012

Несмотря на великолепный ответ Б. Дж. Гомера, я чувствую, что у меня есть решение. Что касается моего тестирования, оно работает.

Концепция: Создайте собственный класс для ячейки xib. Там вы можете дождаться сенсорного события и выполнить передачу программно. Теперь все, что нам нужно, это ссылка на контроллер, выполняющий Segue. Мое решение состоит в том, чтобы установить его в tableView:cellForRowAtIndexPath:.

Пример

У меня есть DetailedTaskCell.xib, содержащий ячейку таблицы, которую я хотел бы использовать в нескольких табличных представлениях:

DetailedTaskCell.xib

Для этой ячейки существует специальный класс TaskGuessTableCell:

enter image description here

Здесь происходит волшебство.

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

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

У меня несколько сегов, но все они имеют одно и то же имя: "FinishedTask". Если вам нужно проявить гибкость, я предлагаю добавить еще одно свойство.

ViewController выглядит следующим образом:

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

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

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

Возможно, есть более элегантные способы достичь того же, но - это работает! :)

16 голосов
/ 21 февраля 2012

Я искал это и нашел этот ответ Ричарда Венейбла. У меня это работает.

iOS 5 включает новый метод для UITableView: registerNib: forCellReuseIdentifier:

Чтобы использовать его, поместите UITableViewCell в перо. Это должен быть единственный корень объект в перо.

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

10 голосов
/ 22 декабря 2016

Свифт 3

Б.Дж. Гомер дал превосходное объяснение, оно помогает мне понять концепцию.К make a custom cell reusable in storyboard, который можно использовать в любом TableViewController, мы должны mix the Storyboard and xib подойти.Предположим, у нас есть ячейка с именем CustomCell, которая должна использоваться в TableViewControllerOne и TableViewControllerTwo.Я делаю это поэтапно.
1. Файл> Создать> Нажмите «Файл»> «Выбрать класс касания какао»> нажмите «Далее»> «Дать имя своему классу» (например, CustomCell)> выберите «Подкласс» как UITableVieCell> Установите флажок «также создать файл XIB» и нажмите «Далее».
2. Настройте ячейку по своему усмотрению и установите идентификатор в инспекторе атрибутов для ячейки, здесь мы установим значение CellIdentifier.Этот идентификатор будет использоваться в вашем ViewController для идентификации и повторного использования ячейки.
3. Теперь нам просто нужно register this cell в нашем ViewController viewDidLoad.Нет необходимости в каком-либо методе инициализации.
4. Теперь мы можем использовать эту пользовательскую ячейку в любом tableView.

В TableViewControllerOne

let reuseIdentifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:reuseIdentifier, for: indexPath) as! CustomCell
    return cell!
}
10 голосов
/ 12 февраля 2012

Би Джей Гомер дал превосходное объяснение того, что происходит.

С практической точки зрения я бы добавил, что, поскольку вы не можете иметь ячейки в качестве XIB-файлов и соединять сегменты, лучше всего выбрать ячейку в качестве XIB - переходы гораздо проще поддерживать, чем макеты и свойства ячеек. в разных местах, и ваши сегменты, скорее всего, будут отличаться от ваших разных контроллеров в любом случае. Вы можете определить переход непосредственно от вашего контроллера табличного представления к следующему контроллеру и выполнить его в коде. ,

Еще одно замечание: то, что ваша ячейка в виде отдельного xib-файла не позволяет подключать какие-либо действия и т. Д. Непосредственно к контроллеру табличного представления (во всяком случае, я не работал - вы не можете определить владельца файла как-нибудь значимое). Я работаю над этим, определяя протокол, которому должен соответствовать контроллер табличного представления ячейки, и добавляя контроллер как слабое свойство, похожее на делегат, в cellForRowAtIndexPath.

5 голосов
/ 11 сентября 2013

Я нашел способ загрузить ячейку для того же VC, не проверенный на segues. Это может быть обходной путь для создания ячейки в отдельном кончике

Допустим, у вас есть один VC и 2 таблицы, и вы хотите создать ячейку в раскадровке и использовать ее в обеих таблицах.

(например: таблица и поле поиска с UISearchController с таблицей результатов, и вы хотите использовать одну и ту же ячейку в обоих)

Когда контроллер запрашивает ячейку, сделайте это:

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

    ContactsCell *cell = [self.YOURTABLEVIEW dequeueReusableCellWithIdentifier:identifier];
  // Ignore the "tableView" argument
}

А вот и ваша клетка из раскадровки

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...