Offscreen UITableViewCells (для расчета размера), не учитывая класс размера?

Я использую классы Auto Layout и size внутри UITableView с ячейками, размер которых зависит от их содержимого. Для этого я использую метод, в котором для каждого типа ячеек вы сохраняете внеэкранный экземпляр этой ячейки и используете systemLayoutSizeFittingSize для определения правильной высоты строки - этот метод прекрасно объясняется в qaru.site/info/1017/... и в другом месте.

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

before and after

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

Теперь я понял, что, вероятно, из-за того, что внеэкранное представление не имеет супервизора или окна, и, как таковое, оно не имеет признаков класса размера, которые нужно ссылаться в точке вызова systemLayoutSizeFittingSize (хотя это и кажется используйте скорректированные ограничения для полей). Я теперь обойдусь этим, добавив ячейку выбора размера экрана как подзадачу UIWindow после ее создания, что дает желаемый результат:

fixed

Вот что я делаю в коде:

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 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 полностью применяли класс размера окна, даже если они не находятся в иерархии представлений? Или есть что-то еще, что мне не хватает? Спасибо.

Ответы

Ответ 1

Обновление до 2015 года:

Apple теперь отвлекает переопределение -traitCollection. Пожалуйста, рассмотрите возможность использования других обходных решений. Из документ:

ВАЖНО

Используйте свойство traitCollection напрямую. Не отменяйте его. Не предоставляйте собственную реализацию.


Исходный ответ:

существующий ответ. Он пояснил, что проблема в том, что:

Предлагаемое обходное решение заключается в том, чтобы временно добавить ячейку в представление таблицы. Однако это НЕ работает, если мы находимся, скажем, -viewDidLoad, в котором traitCollection табличного представления, или вид контроллера представления, или даже самого контроллера представления еще недействителен.

Здесь я предлагаю другое обходное решение, которое должно переопределить traitCollection ячейки. Для этого:

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

  • В пользовательском подклассе добавьте метод - (UITraitCollection *)traitCollection, который переопределяет getter свойства traitCollection. Теперь вы можете вернуть любой действительный UITraitCollection, который вам нравится. Вот пример реализации:

    // Override getter of traitCollection property
    // https://stackoverflow.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://stackoverflow.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 никто не будет называть его получателем и, следовательно, наш метод переопределения.

Ответ 2

Я потратил несколько дней на это, перейдя к использованию классов размера, чтобы упростить изменение размера шрифта на 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 property, но вы можете в iOS8.

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

Здесь обсуждается:
Как адаптивные интерфейсы Xcode 6 будут обратно совместимы с iOS 7 и iOS 6?