屏幕外 UITableViewCells(用于尺寸计算)不考虑尺寸 class?

Offscreen UITableViewCells (for size calculations) not respecting size class?

我在 UITableView 中使用自动布局和大小 classes,其中的单元格 self-size 基于它们的内容。为此,我使用的方法是,对于每种类型的单元格,您都保留该单元格的屏幕外实例,并在其上使用 systemLayoutSizeFittingSize 来确定正确的行高 - 此方法在 [=14= 中有精彩的解释].

在我开始使用大小 classes 之前,这一直很有效。具体来说,我在常规宽度布局中为文本的边距约束定义了不同的常量,因此 iPad 上的文本周围有更多的空白。这给了我以下结果。

似乎新的一组约束得到了遵守(有更多的空白),但行高计算仍然returns与未应用的单元格相同的值大小 class-specific 限制。 屏幕外单元格中的某些布局过程未考虑 window 的大小 class

现在我认为这可能是因为屏幕外视图 没有 超级视图或 window,因此它没有任何大小 class在 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
}

向 window 添加永远不应该绘制的视图对我来说似乎是一个 hack。 有没有办法让 UIViews 完全采用 window 的大小 class 即使它们当前不在视图层次结构中? 或者还有其他方法我迷路了?谢谢。

在转向使用大小 classes 以便在 iPad 与 iPhone 等

上更轻松地更改字体大小后,我花了几天时间在这上面

问题的根源似乎是 dequeueReusableCellWithIdentifier: return 是一个单元格,它没有从中获取其 UITraitCollection 的超级视图。 dequeueReusableCellWithIdentifier:forIndexPath:,另一方面,return 是一个超级视图是 UITableViewWrapperView 的单元格。

我已向 Apple 提交错误报告,因为他们尚未扩展此方法以支持大小 classes;似乎没有记录如何在 iOS7 上处理大小 classes。当您向 UITableView 发送消息请求一个单元格时,它应该 return 一个反映您要向其发送消息的 table 的大小 class 的单元格。 dequeueReusableCellWithIdentifier:forIndexPath:.

就是这种情况

我还注意到,在尝试使用新的自动布局机制时,您经常需要重新加载 viewDidAppear: 中的 table 才能使新机制正常工作。如果没有这个,我会看到与使用 iOS7 方法时遇到的相同问题。

据我所知,似乎无法在 iOS8 上使用自动布局,而在同一代码中使用 iOS7 的旧机制。

现在,我不得不通过将原型单元格添加为 table 的子视图来解决该问题,进行大小计算,然后将其删除:

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];
}

大小 class 相关设置似乎是通过 UITraitCollection 控制的,它是 UIViewController 的只读 属性。对于 iOS7 向后兼容性,这似乎由构建系统处理,作为有一些限制的解决方法。即在 iOS7 上您无法访问 traitCollection property,但在 iOS8.

中可以

考虑到故事板中与视图控制器的紧密耦合以及向后兼容性的工作原理,看起来原型单元必须位于您在 Xcode 中定义的视图控制器的层次结构中。

这里有一个讨论:
How can Xcode 6 adaptive UIs be backwards-compatible with iOS 7 and iOS 6?

2015 年 12 月更新:

Apple 现在不鼓励覆盖 -traitCollection. Please consider using other workarounds. From the doc:

IMPORTANT

Use the traitCollection property directly. Do not override it. Do not provide a custom implementation.


原答案:

很棒。它解释说问题是:

建议的解决方法是临时将单元格添加到 table 视图。但是,如果我们在 -viewDidLoad 中,这将不起作用,其中 table 视图的 traitCollection,或者视图控制器的视图,甚至是视图控制器本身, 尚未生效。

在这里,我提出另一种解决方法,即覆盖单元格的 traitCollection。为此:

  1. 为单元格创建 UITableViewCell 的自定义子类(您可能已经这样做了)。

  2. 在自定义子类中,添加一个- (UITraitCollection *)traitCollection方法,它覆盖了traitCollection属性的getter。现在,您可以 return 任何您喜欢的有效 UITraitCollection。这是一个示例实现:

    // Override getter of traitCollection property
    // 
    - (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;
    }
    

    或者,您可以 return 使用 any one of its create methods 创建的 suitable 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
    // 
    - (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 中,没有人会调用它的 getter,因此我们的重写方法。