屏幕外 UITableViewCells(用于大小计算)不尊重大小类?

Posted

技术标签:

【中文标题】屏幕外 UITableViewCells(用于大小计算)不尊重大小类?【英文标题】:Offscreen UITableViewCells (for size calculations) not respecting size class? 【发布时间】:2015-01-01 22:35:39 【问题描述】:

我在 UITableView 中使用自动布局和大小类,单元格根据其内容自行调整大小。为此,我使用的方法是,对于每种类型的单元格,您保留该单元格的屏幕外实例并在其上使用 systemLayoutSizeFittingSize 来确定正确的行高 - 此方法在 this *** post 和 @987654322 中有很好的解释@。

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

似乎新的一组约束得到遵守(有更多空白),但行高计算仍然返回与未应用特定于大小类约束的单元格相同的值。 屏幕外单元格中的某些布局过程没有考虑窗口的大小等级

现在我认为这可能是因为屏幕外视图没有没有超级视图或窗口,因此在发生systemLayoutSizeFittingSize 调用时它没有任何大小类特征可以引用(尽管它似乎确实使用了调整后的边距约束)。我现在通过在创建 UIWindow 后将屏幕外大小调整单元添加为 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

向窗口添加不应该被绘制的视图对我来说似乎是一种 hack。 有没有办法让 UIViews 完全采用窗口的大小类,即使它们当前不在视图层次结构中? 或者我还缺少什么?谢谢。

【问题讨论】:

【参考方案1】:

2015 年 12 月更新:

Apple 现在不鼓励覆盖 -traitCollection。请考虑使用其他解决方法。来自the doc:

重要

直接使用traitCollection 属性。不要覆盖它。不要提供自定义实现。


原答案:

existing answer 很棒。它解释说问题在于:

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

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

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

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

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

    // Override getter of traitCollection property
    // https://***.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;
    
    

    或者,您可以返回使用any one of its create methods 创建的合适的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://***.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,因此我们的覆盖方法也是如此。

【讨论】:

好主意。希望我在回答 8^) 时能想到这一点。我想下一步是尽可能将其作为类扩展:UITableViewController+TraitCollectionOverride.h 这很有帮助。此外,故事板中基于不同大小类别配置的任何约束也是错误的。您总是会获得为 Any:Any 尺寸类设置的约束及其值。这确实似乎是一个错误或至少是一个巨大的疏忽,即无法从未显示在 tableView 中的情节提要中获取正确配置的单元原型实例。 它似乎对我不起作用。我仍然从 xib 文件中获得 Any Any 约束。对你们有用吗? 覆盖视图的 traitCollection 属性是个好主意。但是,这强烈discouraged by Apple:直接使用 traitCollection 属性。不要覆盖它。不要提供自定义实现。 我认为 Apple 打算这样做是为了防止开发人员滥用尺寸等级。如果他们允许开发人员更改视图的 traitCollection,则意味着您可以将在 常规宽度 设备上显示的视图的尺寸类别更改为尺寸类别compact width 这很可能会弄乱操作系统。例如,系统很难处理从纵向模式到横向模式的设备旋转,因为不清楚视图应该如何反应以适应方向变化。【参考方案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 中定义的视图控制器的层次结构中。

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

【讨论】:

以上是关于屏幕外 UITableViewCells(用于大小计算)不尊重大小类?的主要内容,如果未能解决你的问题,请参考以下文章

html网页外框布局设计总结

resignFirstResponder 到不再出现在屏幕上的 UITableViewCells 上的 UITextViews

在 UITableViewCells 中的 UITextFields 中维护文本

动态调整 UITableViewCells 的大小

将 webbrowser 控件渲染到屏幕外(或隐藏)

UITable 视图项有时会在选择时消失(取决于屏幕大小)