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

216

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

Вот где я сталкиваюсь с проблемой.

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

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

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

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

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

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

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

Клифф W
источник

Ответы:

205

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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

Би Джей Гомер
источник
Ах, я понял. Вы прибили мое недоразумение - иерархия представления полностью независима от моего класса. Очевидное в ретроспективе! Спасибо за отличный ответ.
Утес
Кажется, это больше не невозможно: stackoverflow.com/questions/8574188/…
Богатая Аподака
7
@RichApodaca Я упомянул это решение в своем ответе. Но это не в раскадровке; это в отдельном перо. Таким образом, вы не можете подключать сегы или делать другие вещи, связанные с раскадровкой. Следовательно, это не полностью решает начальный вопрос.
Би Джей Гомер
Начиная с XCode8, следующий обходной путь работает, если вам нужно решение только для раскадровки. Шаг 1) Создайте ячейку-прототип в виде таблицы в ViewController # 1 и свяжите ее с пользовательским классом UITableViewCell. Шаг 2) Скопируйте / вставьте эту ячейку в представление таблицы ViewController # 2. Со временем вам придется помнить, что нужно вручную распространять обновления для копий ячейки, удаляя копии, которые вы сделали в раскадровке, и вставляя обратно в обновленный прототип.
jengelsma
Хороший ответ, у меня есть дополнительный вопрос:> «Раскадровка, по сути, не намного больше, чем набор файлов .xib», если это так, почему так сложно встроить xib в раскадровка?
Willcwf
58

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

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

пример

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

DetailedTaskCell.xib

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

введите описание изображения здесь

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

// 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

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

ericteubert
источник
1
Спасибо, я реализую этот подход в моем проекте. Вместо этого вы можете переопределить этот метод, чтобы вам не нужно было получать indexPath и выбирать строку самостоятельно: - (void) setSelected: (BOOL) selected animated: (BOOL) animated {[super setSelected: selected animated: animated]; if (selected) [self.controller executeSegueWithIdentifier: self.segue sender: self]; } Я думал, что super выберет ячейку при вызове [super touchesEnded: touch withEvent: event] ;. Вы знаете, когда он выбран, если не там?
thejaz
9
Обратите внимание, что с этим решением вы запускаете переход каждый раз, когда касание заканчивается внутри ячейки. Это включает в себя, если вы просто прокручиваете ячейку, а не пытаетесь выбрать ее. Возможно, вам удастся переопределить удачу -setSelected:в ячейке и вызвать переход только при переходе от NOк YES.
Би Джей Гомер
Мне больше повезло setSelected:, Би Джей. Спасибо. Действительно, это не элегантное решение (оно кажется неправильным), но в то же время оно работает, поэтому я использую его, пока это не будет исправлено (или что-то изменится в суде Apple).
Бен Крегер
16

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

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

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

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

Odrakir
источник
10

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

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

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

jrturton
источник
10

Свифт 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!
}
Кунал Кумар
источник
5

Я нашел способ загрузить ячейку для того же 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
}

И вот у вас есть ваша клетка из раскадровки

Жуан Нуньес
источник
Я попробовал это, и это, кажется, работает ОДНАКО клетки никогда не используются повторно. Система всегда создает и освобождает новые ячейки каждый раз.
ГилройКилрой
Это та же рекомендация, что и в разделе « Добавление панели поиска в табличное представление с раскадровками» . Если вам интересно, есть более подробное объяснение этого решения (поиск tableView:cellForRowAtIndexPath:).
чувственный
но это меньше текста и отвечает на вопрос
Жуан Нуньес