Внеэкранные 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 ячейки. Для этого:

  1. Создайте собственный подкласс UITableViewCell для клетки (что вы, вероятно, уже сделали).

  2. В пользовательском подклассе добавьте - (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?

Другие вопросы по тегам