Внеэкранные UITableViewCells (для расчета размера), не относящиеся к классу размера?
Я использую Auto Layout и классы размеров внутри UITableView с ячейками, размер которых зависит от их содержимого. Для этого я использую метод, где для каждого типа ячеек вы держите внеэкранный экземпляр этой ячейки и используете systemLayoutSizeFittingSize
на том, чтобы определить правильную высоту строки - этот метод прекрасно объясняется в этом посте Stackru и в других местах.
Это прекрасно работало, пока я не начал использовать классы размера. В частности, я определил различные константы для ограничения полей для текста в макетах Regular Width, поэтому вокруг iPad больше текста. Это дает мне следующие результаты.
Похоже, что новый набор ограничений соблюдается (больше пробелов), но вычисление высоты строки все равно возвращает то же значение, что и для ячейки, которая не применяла ограничения, специфичные для класса размера. Некоторая часть процесса макета в ячейке вне экрана не учитывает класс размера окна.
Теперь я подумал, что это, вероятно, потому, что внеэкранное представление не имеет суперпредставления или окна, и поэтому у него нет никаких черт класса размера, на которые можно ссылаться в точке systemLayoutSizeFittingSize
происходит вызов (хотя кажется, что он использует скорректированные ограничения для полей). Теперь я работаю над этим, добавляя ячейку закадрового размера в качестве подпредставления UIWindow после его создания, что дает желаемый результат:
Вот что я делаю в коде:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let contentItem = content[indexPath.item]
if let contentType = contentItem["type"] {
// Get or create the cached layout cell for this cell type.
if layoutCellCache.indexForKey(contentType) == nil {
if let cellIdentifier = CellIdentifiers[contentType] {
if var cachedLayoutCell = dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell {
UIApplication.sharedApplication().keyWindow?.addSubview(cachedLayoutCell)
cachedLayoutCell.hidden = true
layoutCellCache[contentType] = cachedLayoutCell
}
}
}
if let cachedLayoutCell = layoutCellCache[contentType] {
// Configure the layout cell with the requested cell's content.
configureCell(cachedLayoutCell, withContentItem: contentItem)
// Perform layout on the cached cell and determine best fitting content height.
cachedLayoutCell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), 0);
cachedLayoutCell.setNeedsLayout()
cachedLayoutCell.layoutIfNeeded()
return cachedLayoutCell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
}
}
fatalError("not enough information to determine cell height for item \(indexPath.item).")
return 0
}
Добавление видов к окну, которые никогда не должны быть нарисованы, кажется мне хаком. Есть ли способ, чтобы UIViews полностью приняли класс размера окна, даже если они не находятся в иерархии представления? Или я что-то пропустил? Благодарю.
2 ответа
Обновление в декабре 2015 года:
Apple теперь не одобряет переопределение -traitCollection
, Пожалуйста, рассмотрите возможность использования других обходных путей. Из документа:
ВАЖНЫЙ
Использовать
traitCollection
собственность напрямую. Не отменяйте это. Не предоставляйте пользовательскую реализацию.
Оригинальный ответ:
Существующий ответ великолепен. Это объяснило, что проблема в том, что:
-dequeueReusableCellWithIdentifier:
возвращает ячейку без действительногоcell.traitCollection
, а такжеcell.traitCollection
являетсяreadonly
,
Предложенный обходной путь - временно добавить ячейку в табличное представление. Однако, это НЕ работает, если мы, скажем, -viewDidLoad
, в котором traitCollection
представления таблицы, или представления контроллера представления, или даже самого контроллера представления, еще не действительны.
Здесь я предлагаю другой обходной путь, который должен переопределить traitCollection
ячейки. Для этого:
Создайте собственный подкласс
UITableViewCell
для клетки (что вы, вероятно, уже сделали).В пользовательском подклассе добавьте
- (UITraitCollection *)traitCollection
метод, который переопределяет получательtraitCollection
имущество. Теперь вы можете вернуть любой действительныйUITraitCollection
тебе нравится. Вот пример реализации:// Override getter of traitCollection property // https://stackru.com/a/28514006/1402846 - (UITraitCollection *)traitCollection { // Return original value if valid. UITraitCollection* originalTraitCollection = [super traitCollection]; if(originalTraitCollection && originalTraitCollection.userInterfaceIdiom != UIUserInterfaceIdiomUnspecified) { return originalTraitCollection; } // Return trait collection from UIScreen. return [UIScreen mainScreen].traitCollection; }
В качестве альтернативы вы можете вернуть подходящий
UITraitCollection
создан с использованием любого из его методов создания, например:+ (UITraitCollection *)traitCollectionWithDisplayScale:(CGFloat)scale + (UITraitCollection *)traitCollectionWithTraitsFromCollections:(NSArray *)traitCollections + (UITraitCollection *)traitCollectionWithUserInterfaceIdiom:(UIUserInterfaceIdiom)idiom + (UITraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + (UITraitCollection *)traitCollectionWithVerticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
Или вы можете даже сделать его более гибким, выполнив это:
// Override getter of traitCollection property // https://stackru.com/a/28514006/1402846 - (UITraitCollection *)traitCollection { // Return overridingTraitCollection if not nil, // or [super traitCollection] otherwise. // overridingTraitCollection is a writable property return self.overridingTraitCollection ?: [super traitCollection]; }
Этот обходной путь совместим с iOS 7, потому что traitCollection
свойство определено в iOS 8+, и поэтому в iOS 7 никто не будет вызывать его геттер, и, следовательно, наш переопределяющий метод.
Я потратил несколько дней на это после перехода к использованию классов размера, чтобы упростить изменение размера шрифта на iPad по сравнению с iPhone и т. Д.
Корень проблемы, кажется, в том, что dequeueReusableCellWithIdentifier:
возвращает ячейку без суперпредставления, из которой она получает UITraitCollection
, dequeueReusableCellWithIdentifier:forIndexPath:
с другой стороны, возвращает ячейку, суперпредставление которой UITableViewWrapperView
,
Я поднял сообщение об ошибке в Apple, так как они не расширили этот метод для поддержки классов размеров; Кажется, не задокументировано, что делать с размерами классов на iOS7. Когда вы отправляете сообщение UITableView
запрашивая ячейку, она должна вернуть ячейку, которая отражает класс размера таблицы, в которую вы отправляете сообщение. Это случай для dequeueReusableCellWithIdentifier:forIndexPath:
,
Я также заметил, что при попытке использовать новый механизм автоматического размещения, вам часто нужно перезагрузить таблицу в viewDidAppear:
чтобы новый механизм работал правильно. Без этого я вижу ту же проблему, что и у меня, используя подход iOS7.
Насколько я могу судить, не представляется возможным использовать автоматическое расположение на iOS8 и старый механизм для iOS7 из того же кода.
На данный момент мне пришлось прибегнуть к решению проблемы, добавив ячейку-прототип в качестве подпредставления таблицы, выполнив вычисление размера, а затем удалив его:
UITableViewCell *prototype=nil;
CGFloat prototypeHeight=0.0;
prototype=[self.tableView dequeueReusableCellWithIdentifier:@"SideMenuCellIdentifier"];
// Check for when the prototype cell has no parent view from
// which to inherit size class related constraints.
BOOL added=FALSE;
if (prototype.superview == nil){
[self.tableView addSubview:prototype];
added=TRUE;
}
<snip ... Setup prototype cell>
[prototype setNeedsLayout];
[prototype layoutIfNeeded];
CGSize size = [prototype.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
prototypeHeight=size.height+1; // Add one for separator
// Remove the cell if added. Leaves it when in iOS7.
if (added){
[prototype removeFromSuperview];
}
Настройки, связанные с классом размеров, по-видимому, управляются через UITraitCollection
который является только для чтения свойством UIViewController
, Для обратной совместимости iOS7 это, кажется, обрабатывается системой сборки как обходной путь с некоторыми ограничениями. то есть на iOS7 вы не можете получить доступ к traitCollection
свойство, но вы можете в iOS8.
Учитывая тесную связь с контроллером представления из раскадровки и то, как работает обратная совместимость, похоже, что ячейка прототипа должна находиться в иерархии контроллера представления, который вы определили в XCode.
Здесь обсуждается это:
Как адаптивные интерфейсы Xcode 6 могут быть обратно совместимы с iOS 7 и iOS 6?