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

Ken*_*ker 15 uikit ios autolayout size-classes

我在UITableView中使用自动布局和大小类,单元格根据其内容自行调整大小.为此,我使用的方法对于每种类型的单元格,您保留该单元格的屏幕外实例并使用systemLayoutSizeFittingSize它来确定正确的行高 - 此方法在此StackOverflow帖子其他地方进行了很好的解释.

这很有效,直到我开始使用大小类.具体来说,我在常规宽度布局中为文本的边距约束定义了不同的常量,因此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
}
Run Code Online (Sandbox Code Playgroud)

向窗口添加不应该被绘制的视图对我来说似乎是一个黑客.有没有办法让UIViews完全采用窗口的大小类,即使它们当前不在视图层次结构中?或者还有其他我想念的东西?谢谢.

Pan*_*ang 8

2015年12月更新:

苹果现在不鼓励压倒一切-traitCollection.请考虑使用其他解决方法.来自doc:

重要

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


原答案:

现有的答案是伟大的.它解释了问题是:

建议的解决方法是将单元格临时添加到表视图中.然而,这并不工作,如果我们都在,说-viewDidLoad,在该traitCollection表视图,或视图控制器,甚至视图控制器本身的看法,尚未生效.

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

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

  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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    或者,您可以UITraitCollection使用其任何一种创建方法返回合适的创建方法,例如:

    + (UITraitCollection *)traitCollectionWithDisplayScale:(CGFloat)scale
    + (UITraitCollection *)traitCollectionWithTraitsFromCollections:(NSArray *)traitCollections
    + (UITraitCollection *)traitCollectionWithUserInterfaceIdiom:(UIUserInterfaceIdiom)idiom
    + (UITraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
    + (UITraitCollection *)traitCollectionWithVerticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
    
    Run Code Online (Sandbox Code Playgroud)

    或者,您甚至可以通过这样做使其更灵活:

    // 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];
    }
    
    Run Code Online (Sandbox Code Playgroud)

此解决方法与iOS 7兼容,因为该traitCollection属性是在iOS 8+中定义的,因此,在iOS 7中,没有人会调用其getter,因此我们的首要方法.


Ror*_*nel 5

我花了好几天的时间来使用尺寸类来改变iPad上的字体大小,而不是iPhone等.

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

我已经向Apple提出了一个bug报告,因为他们没有扩展这个方法来支持大小类; 似乎没有记录如何处理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];
}
Run Code Online (Sandbox Code Playgroud)

大小类相关的设置似乎是通过a控制的UITraitCollection,它是a的只读属性UIViewController.对于iOS7向后兼容性,这似乎由构建系统处理,作为一些解决方案.即在iOS7上你无法访问该traitCollection属性,但你可以在iOS8中访问.

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

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