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

В iOS5 ввели замечательную фичу — Storyboard, а также возможность создавать прототипы ячеек прямо внутри создаваемой таблицы (которые, тем не менее, компилятся в отдельные NIB-ы). Однако новый функционал решили не внедрять в обычные XIB-ы.
Меня немного озадачило, что в свежем Xcode все-таки можно создать UITableViewController, в котором сразу будет таблица, и даже прототип ячейки. Однако при компиляции Xcode выдаст ошибку, что так делать мол нельзя.
Так мы подошли к вопросу «а зачем»:
Допустим, есть два больших сториборда. Почему два? Потому что, если пихать гору вьюшек, кнопочек и табличек в один, то даже новенький и резвый (правда год назад) MacBook Pro Retina 13" превращается в тыкву.
Итак, есть два сториборда, и, допустим, оба они должны открыть один и тот же контроллер при разном стечении обстоятельств.
Пытливый читатель заметит, что в iOS9 ввели ссылки на внешний storyboard, но что если проект требует поддержки iOS8, а то и 7? К сожалению, обратной совместимости ребята из Купертино добавлять не любят.
Возможным решением будет создать ViewController без View и внешний Xib с таким же именем класса, чтобы он автоматически подгрузился методом loadView. Или явно устанавливаем свойство nibName:

Кажется, раньше для этого было явное поле, но в Xcode 7 я его не нашел.
И тут мы сталкиваемся с проблемой, что прототипы ячеек для таблицы придется класть во внешние Xib-ы, причем по одному в файл, и явно регистрировать их в коде. После использования Storyboard совсем не хочется так делать.
И вот как можно поступить (код будет на Objective C, так как далее используется чёрная магия):
Создаем .h файл со следующим содержанием:
@interface UITableView (XibCells)
@property (nonatomic, strong) IBOutletCollection(UITableViewCell) NSArray* cellPrototypes;
@end
Это позволит закинуть в файл Interface Builder ячейки (правда, не внутрь таблицы, а рядом), и подключить их к IBOutletCollection cellPrototypes

Теперь надо как-то подсунуть загруженные ячейки таблице, чтобы она их подгружала по мере необходимости.
Для этого в .m файле создадим наследника UINib с предварительно загруженными данными и переопределим метод instantiateWithOwner:options:
@interface PrepopulatedNib: UINib
@property (nonatomic, strong) NSData* nibData;
@end
@implementation PrepopulatedNib
+ (instancetype)nibWithObjects:(NSArray*)objects {
  PrepopulatedNib* nib = [[self alloc] init];
  nib.nibData = [NSKeyedArchiver archivedDataWithRootObject:objects];
  return nib;
}
- (NSArray *)instantiateWithOwner:(id)ownerOrNil options:(NSDictionary *)optionsOrNil {
  return [NSKeyedUnarchiver unarchiveObjectWithData:_nibData];
}
@end
При инициализации объекта PrepopulatedNib переданный массив архивируется в NSData с помощью NSKeyedArchiver.
Далее UITableView вызывает метод instantiateWithOwner:nil options:nil, и мы разархивируем массив обратно, создавая таким образом копию объектов. Ячейки, полученные таким образом 100% идентичны, так как только что были разархивированы из NIB-a и соответствуют протоколу NSCoding.
Последний штрих: заставить таблицу связать переданные ячейки и PrepopulatedNib:
@implementation UITableView (XibCells)
- (void)setCellPrototypes:(NSArray*)cellPrototypes {
  for (UITableViewCell* cell in cellPrototypes) {
      [self registerNib:[PrepopulatedNib nibWithObjects:@[cell]] forCellReuseIdentifier:cell.reuseIdentifier];
  }
}
@end
Теперь таблица может работать, как будто ее загрузили из Storyboard. Тут можно немного заморочиться и при первом вызове возвращать оригинальные объекты ячеек, переданные в массиве, чтобы ресурсы не пропадали даром, но они нам пригодятся позже:
Итак, об автоматической калькуляции высоты ячеек
В iOS8 наконец-то ввели out-of-the-box вычисление высоты ячеек при использовании Layout Constraints (хотя и глючило оно сильно). В iOS9 эту функцию отполировали и добавили Stack Views. Опять же ни о какой обратной совместимости речи нет.
Предлагаю удобное решение этой задачи с использованием кода для подгрузки ячеек из одного XIB-а
Одним из способов вычисления высоты является хранение по одному невидимому экземпляру UITableViewCell с установленными constraint-ами. Для этого в процедуре tableView:heightForRowAtIndexPath: в таком экземпляре устанавливается item/text будущей ячейки, и, после вызова метода [cell layoutIfNeeded], возвращается cell.frame.size.height.
Воспользуемся нашими предзагруженными ячейками для этого способа. Для этого будем хранить ячейки в NSDictionary, ассоциированном с таблицей. Для этого нужно добавить в .m файл инструкцию
#import <objc/runtime.h>В методе setCellPrototypes: создадим NSDictionary с ячейками, где ключ — reuseIdentifier:
@implementation UITableView (XibCells)
static char cellPrototypesKey;
- (void)setCellPrototypes:(NSArray<UITableViewCell *> *)cellPrototypes {
  NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:cellPrototypes.count];
  for (UITableViewCell* cell in cellPrototypes) {
      [self registerNib:[PrepopulatedNib nibWithObjects:@[cell]] forCellReuseIdentifier:cell.reuseIdentifier];
      dict[cell.reuseIdentifier] = cell;
  }
  objc_setAssociatedObject(self, &cellPrototypesKey, cellPrototypes, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSArray*)cellPrototypes { return nil; } //Чтобы не было warning-a
- (UITableViewCell *)cellPrototypeWithIdentifier:(NSString *)reuseIdentifier {
  NSDictionary* dict = (NSDictionary*)objc_getAssociatedObject(self, &cellPrototypesKey);
  return dict[reuseIdentifier];
}
@end
Объявление cellPrototypeWithIdentifier: нужно будет вынести в .h файл, чтобы его можно было использовать в коде.
@interface UITableView (XibCells)
@property (nonatomic, strong) IBOutletCollection(UITableViewCell) NSArray* cellPrototypes;
- (UITableViewCell*)cellPrototypeWithIdentifier:(NSString*)reuseIdentifier;
@end
Теперь в коде datasource можно использовать прототипы для вычисления высоты:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    id cellItem = _items[indexPath.section][indexPath.row];
    MyTableViewCell* cell = [tableView cellPrototypeWithIdentifier:@"Cell"];
    cell.item = cellItem;
    [cell layoutIfNeeded];
    return cell.frame.size.height;
}
Код нарочно не представляет собой all-in-one решения, так как является Proof of concept и предоставляется исключительно в ознакомительных целях.
Спасибо за внимание.
Комментарии (3)
 - aspcartman26.11.2015 00:35+3- Статья рассказывает о двух (имхо — совершенно) не связанных задачах и их так же не связанных решениях. Думаю было бы лучше разобрать что-то одно, но очень подробно, в техническом плане: не просто, куда нажать, чтобы заработало, а глубокий экскурс в то, как это вообще работает. 
 
 Пояснение:
 Я не пользуюсь сторибордами, ксибами и строю весь интерфейс на аутолейауте с помощью KeepLayout. Сторонник мнения, что сториборды — зло. По этому вопросы могут показаться странными.
 
 Вопросы:
 1. Почему не реализовать нужные клетки в отдельных ксибах, по старинке, и просто зарегистрировать нужные классы так же, как вы делаете в статье? Кода, как либо связанного с IBOutletCollection не будет, и клетки могут быть использованы в разных контроллерах, без какой-либо привязки к сторибордам.
 
 2. Что значит- И тут мы сталкиваемся с проблемой, что прототипы ячеек для таблицы придется класть во внешние Xib-ы, причем по одному в файл, и явно регистрировать их в коде. После использования Storyboard совсем не хочется так делать. ? Вы же именно это и делаете, регистрируете каждый по отдельности, только создаете ксибу для целого вью контроллера. А если клетки используются в разных контроллерах? Таким образом вы жестко привязали клетки к классу контроллера, но с теоретической стороны связи быть не должно и потребуются дополнительные телодвижения для того, чтобы позже это исправить, плюс вы уже совершили больше телодвижений, чем можно было для достижение цели строить интерфейс графически.
 
 3. Что значит- Теперь таблица может работать, как будто ее загрузили из Storyboard. ? В чем отличие поведения UITableView, созданной в сториборде, от онной, созданной в коде?
 
 4.
 Серьезно? Вы только что все сломали. Обьясните, пожалуйста, читателям, почему класс, которому добавили аксессорный метод, возвращающий nil, когда либо будет возвращать по этому проперти не nil? Расскажите, как работает Extensions в Obj-C? Метод, который я имплементировал в Extension, когда и как добавляется в класс? Расскажите, как работает IBOutlet/IBOutletCollection для property? При загрузке из nib выполняется -setPropertyName: или же в рантайме имплементируется геттер -propertyName? Если первое, то почему нет краша, ведь поведение проперти по умолчанию это- - (NSArray*)cellPrototypes { return nil; } //Чтобы не было warning-a
 , а ClassExtensions не умеет добавлять iVar'ы в класс по очевидным причинам, и для Extensions пропертя делается (скорее всего, я не компилировал такой случай на моей практике без имплементации аксессорных методов в extension, чтобы посмотреть, что будет)- @sythnesize propertyName = _propertyName;- @dynamic propertyName
 и ожидает, что вы имплементируете свои методы, и если вы этого не сделаете, то будет краш. Если же второе, то почему ваша имплементация, если код рабочий, оверрайдится имплементацией для IBOutlet? Это противоречит ожидаемому поведению dynamic пропертей в Foundation и ко. Например NSManagedObject в CoreData. Да и о каком конкретно ворнинге речь, и почему вы просто от него отмахиваетесь?
 
 Предложения:
 5. Все связанное с высотой клеток, имхо, стоит вынести в классы самих клеток, а точнее имплементировать их суперкласс, который будет хранить «невидимые» клетки по именам классов и блоки их инициализаций, заместо нагружения лишним кодом ваш контроллер.
 
 6.
 Вы только что создали бестолковую переменную, которая торчит в скопе всего файла. Вместо этого предлагается (http://nshipster.com/associated-objects/) делать- static char cellPrototypesKey; // ... objc_setAssociatedObject(self, &cellPrototypesKey, cellPrototypes, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 - objc_setAssociatedObject(self, @selector(getterName), cellPrototypes, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - mallexxx26.11.2015 09:28+1- Статья рассказывает о двух (имхо — совершенно) не связанных задачах 
 Действительно, вторая задача всплыла спонтанно при написании статьи. Возможно, вы правы, но статья не задумывалась как пособие для совсем новичков (о чем я честно и предупреждаю в начале), а чтобы показать как можно (если очень хочется) решить данные конкретные задачи.
 А заодно возможно кому-то привести пример использования наследования, категорий(extensions), outlet-collections, и одного из способов вычисления высоты ячеек.
 - Почему не реализовать нужные клетки в отдельных ксибах 
 Считайте это долгом перфекционизму, если в проекте очень много файлов, возникает желание сделать их меньше. Опять же, никто не возражает против дизайна ячеек внутри таблицы в сториборде, и их тоже не переиспользовать в других местах, но иногда это и не нужно. Скорее вопрос а почему нет? :)
 - Да и о каком конкретно ворнинге речь, и почему вы просто от него отмахиваетесь 
 Чтобы иметь возможность проставить аутлет в Interface Builder нужна пропертя, по сути только сеттер, мы этот массив в таблице не храним и геттер нам не нужен. В категории (exntension) ivar-ы не сгенерятся, как вы верно заметили, и будет варнинг, что геттера нет, и его вызов приведет к крашу.
 - Все связанное с высотой клеток, имхо, стоит вынести в классы самих клеток 
 Согласен, иногда так лучше. Но для простых ячеек я предпочитаю не плодить сущности, а просто накидать лейблов и загружать их по тэгу (да-да, я знаю что Apple на WWDC сказала никогда-никогда так не делать, но что страшного если в ячейке 2 лэйбла?)
 - Вы только что создали бестолковую переменную, которая торчит в скопе всего файла 
 Спасибо, действительно, учту, не думал о такой реализации.
 
 
           
 
nepster
под iOS8 работает :)