离线UITableViewCells(用于大小计算)不尊重大小class?

我在UITableView中使用自动布局和大小类,并根据其内容使用自定义大小的单元格。 为此,我使用的方法对于每种types的单元格,都会保留该单元格的非屏幕实例,并使用systemLayoutSizeFittingSize来确定正确的行高度 – 此方法在此StackOverflowpost和其他位置有很好的解释。

这工作很好,直到我开始使用大小类。 具体而言,我已经定义了常规宽度布局中文本的边距约束的不同常量,因此iPad上的文本周围会有更多的空白。 这给了我以下结果。

之前和之后

看起来,新的一组约束被认可(有更多的空白),但是行高计算仍然返回与没有应用大小类特定约束的单元相同的值。 屏幕外单元中布局过程的某些部分没有考虑窗口的尺寸等级

现在我认为这可能是因为屏幕外视图没有超视图或窗口,因此在systemLayoutSizeFittingSize调用发生时没有任何大小的类特征可以引用(即使它似乎使用调整后的约束利润率)。 我现在通过添加屏幕尺寸的单元格作为UIWindow创build后的子视图来解决这个问题,从而得到所需的结果:

固定

以下是我在代码中所做的:

 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完全采用窗口的大小类,即使他们目前不在视图层次? 还是有什么我失踪? 谢谢。

2015年12月更新:

苹果现在不鼓励重写-traitCollection 。 请考虑使用其他解决方法。 从文档 :

重要

直接使用traitCollection属性。 不要重写它。 不要提供自定义的实现。


原始答案:

现有的答案是伟大的。 它解释说问题是:

  • -dequeueReusableCellWithIdentifier:返回一个没有有效的cell.traitCollection的单元格,
  • cell.traitCollectionreadonly

build议的解决方法是临时将单元格添加到表视图。 然而,如果我们在-viewDidLoad ,那么这-viewDidLoad ,在这种traitCollection下,表视图的traitCollection ,视图控制器的视图,甚至视图控制器本身都是无效的。

在这里,我提出了另一个解决方法,即重写单元格的traitCollection 。 要做到这一点:

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

  2. 在自定义子类中,添加- (UITraitCollection *)traitCollection方法,该方法覆盖traitCollection属性的getter。 现在,你可以返回你喜欢的任何有效的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; } 

    或者,您可以返回使用其任何一种创build方法创build的合适的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中,没有人会调用其getter,因此也是我们的重写方法。

在转移到使用大小类别来更改iPad的字体大小与iPhone等之后,我花了数天时间

问题的根源似乎是dequeueReusableCellWithIdentifier:返回一个没有UITraitCollection视图的单元,从中获取它的UITraitCollectiondequeueReusableCellWithIdentifier:forIndexPath:另一方面,返回一个其dequeueReusableCellWithIdentifier:forIndexPath:视图是UITableViewWrapperView

我已经向苹果公司提出了一个错误报告,因为他们没有扩展这个方法来支持大小类。 似乎没有文档说明如何处理iOS7上的大小类。 当你发送一个消息给一个UITableView请求一个单元格时,它应该返回一个反映你发送消息的表的大小类别的消息。 这是dequeueReusableCellWithIdentifier:forIndexPath:

我也注意到,当试图使用新的自动布局机制时,您经常需要重新加载viewDidAppear:的表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控制, UITraitCollectionUIViewController的只读属性。 对于iOS7的向后兼容性,这似乎是由构build系统作为解决一些限制处理。 即在iOS7上,您不能访问traitCollection属性 ,但是您可以在iOS8中。

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

这里有一个讨论:
Xcode 6自适应UI如何向后兼容iOS 7和iOS 6?