iOS-如何使UICollectionViewCell根据其内容调整其高度? (包含一个UITableView)

时间:2018-12-05 13:27:41

标签: ios swift uitableview uicollectionviewcell intrinsic-content-size

我不知道为什么设计能适应其内容的单元格如此复杂。它不需要那么多代码,我仍然不明白为什么UIKit无法正确处理此问题。

无论如何,这是我的问题我已编辑了整个帖子):

我有一个UICollectionViewCell,其中包含一个UITableView

这是我的sizeForItem方法:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {

        var cellWidth: CGFloat = collectionView.bounds.size.width 
        var cellHeight: CGFloat = 0

        let cellConfigurator = items[indexPath.item].cellConfigurator

        if type(of: cellConfigurator).reuseId == "MoonCollectionViewCell" {
            if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type(of: cellConfigurator).reuseId, for: indexPath) as? MoonCollectionViewCell {
               cell.contentView.layoutIfNeeded()
               let size = cell.selfSizedTableView.intrinsicContentSize
               cellHeight = size.height
            }
        }

        return CGSize.init(width: cellWidth, height: cellHeight)
    }

sizeForItemcellForItem之前被调用,这就是layoutIfNeeded的原因,因为我无法获得正确的intrinsic content size

我已按照建议删除了XIB,并在情节提要中设计了UICollectionViewCell。

这是我在Storyboard中设计的UICollectionViewCell(仅在XIB文件中设计了UITableViewCell)

我只在UICollectionViewCell中添加了一个UITableView。
我希望UICollectionViewCell根据tableView的高度调整其大小。

现在这是我的tableView

我已经从post创建了UITableView的子类

class SelfSizedTableView: UITableView {
    var maxHeight: CGFloat = UIScreen.main.bounds.size.height

    override func reloadData() {
        super.reloadData()
        self.invalidateIntrinsicContentSize()
        self.layoutIfNeeded()
    }

    override var intrinsicContentSize: CGSize {
        let height = min(contentSize.height, maxHeight)
        return CGSize(width: contentSize.width, height: height)
    }
}

请注意,我有disabled scrolling,我有dynamic prototype用于tableView单元格,样式为grouped

编辑::检查配置方法,它来自我用来以通用方式配置所有UICollectionViewCell

的协议
func configure(data: [MoonImages]) {

        selfSizedTableView.register(UINib.init(nibName: "MoonTableViewCell", bundle: nil), forCellReuseIdentifier: "MoonTableViewCell")

        selfSizedTableView.delegate = self
        selfSizedTableView.dataSource = moonDataSource

        var frame = CGRect.zero
        frame.size.height = .leastNormalMagnitude
        selfSizedTableView.tableHeaderView = UIView(frame: frame)
        selfSizedTableView.tableFooterView = UIView(frame: frame)

        selfSizedTableView.maxHeight = 240.0
        selfSizedTableView.estimatedRowHeight = 40.0
        selfSizedTableView.rowHeight = UITableView.automaticDimension

        moonDataSource.data.addAndNotify(observer: self) { [weak self] in
            self?.selfSizedTableView.reloadData()
        }

        moonDataSource.data.value = data

    }

仅供参考 dataSource是具有动态值(泛型)和观察者模式的自定义dataSource,用于在设置数据后重新加载collection / tableView。

启动应用程序时,我也会收到此警告。

  

[CollectionView]检测到尝试更新布局信息   虽然已经在计算布局(即可重入)   呼叫)。这将导致意外行为或崩溃。这可能   如果在调用委托时触发了布局传递,则会发生这种情况。

有关如何处理此问题的任何提示或建议吗?

因为我面临一个奇怪的行为,就像我的sizeForItem使用随机值。 UICollectionViewCell的高度与我的UITableView 内部内容大小的高度不同。

如果我的UITableView中有2行,则UICollectionView并不总是等于此大小。我真的不知道该怎么实现...

我应该invalideLayout吗?

3 个答案:

答案 0 :(得分:2)

也许这不是您想要的答案,但这是我的两分钱。对于您的特定要求,更好的解决方案是远离UITableView,而使用UIStackView或您的自定义容器视图。

这是为什么:

  1. UITableViewUIScrollView的子类,但是由于您已禁用其滚动功能,因此不需要UIScrollView
  2. UITableView主要用于重用单元格,以提高性能并使代码更结构化。但是,由于您要使其内容大小一样大,因此不会重复使用任何单元格,因此UITableView的功能不会得到任何利用。

因此,实际上您不需要,您不应该在UITableView内使用UIScrollViewUICollectionViewCell来满足您的要求。

如果您同意以上部分,则可以从我们的实践中学到一些东西:

  1. 我们始终将大多数基础视图和代码逻辑(主要是数据组装)移至基于UIView的自定义视图中,而不是直接放入UITableViewCellUICollectionViewCell。然后将其添加到UITableViewCellUICollectionViewCell的{​​{1}}和设置约束中。通过这种结构,我们可以在更多场景中重用我们的自定义视图。
  2. 对于与您类似的要求,我们将创建一个工厂类来创建类似于您为contentView创建“单元”的“行”,将它们添加到垂直UITableView中,创建约束确定UIStackView的宽度。自动布局将处理其余的事情。
  3. 在与UIStackView一起使用时,要计算所需的高度,可以在单元格的UICollectionViewCell函数内部,使用preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes)计算高度,进行一些检查并返回。另外,请记住,contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)的宽度更改时会使布局无效。

答案 1 :(得分:1)

我试图在发布的代码中找到罪魁祸首,但是似乎有很多活动的部分。因此,我将尝试给出一些提示,希望能有所帮助。

理论上(iOS 12有一些警告),自行设置UICollectionViewCells的大小应该并不困难。您基本上可以将collectionViewLayout.estimedItemSize设置为任何值(以下是首选常量),如下所示:

(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

然后,您必须确保以可自行调整大小的方式设置单元格中的约束。即自动布局可以计算单元格的宽度和高度。您正在提供tableView的intrinsicContentSize,它由其所有四个端的超级视图包裹,因此应该可以。

如上所述,一旦设置了estimatedItemSize,就不应实现返回大小的委托方法:

func collectionView(_: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt: IndexPath) -> CGSize

可以在此处找到快速教程以作进一步参考:https://medium.com/@wasinwiwongsak/uicollectionview-with-autosizing-cell-using-autolayout-in-ios-9-10-84ab5cdf35a2

正如我在理论上说的那样,这并不难,但是在iOS 12上自动调整单元格大小似乎不起作用,请参见此处In iOS 12, when does the UICollectionView layout cells, use autolayout in nib

如果我处于您的位置,我将从头开始,逐步增加复杂性:

  • 尝试通过使用简单的UIView和intrinsicContentSize的覆盖来实现自定义单元格;可能是通过使用iOS 11.4 SDK排除与iOS 12有关的问题(最简单的方法是下载最新的Xcode 9并从那里工作);如果不可能,请在此步骤中修复iOS 12
  • 将简单视图替换为表格视图(每个视图也可能具有动态调整大小)
  • 执行tableview重载数据流,即动态调整大小功能
  • 如果一切正常,请修复iOS 12并迁移到iOS 12

希望这会有所帮助。

顺便说一句,控制台中的警告可能是由于在委托方法中调用layoutIfNeeded()引起的。它会触发立即进行布局遍历,而UICollectionView会在收集所有尺寸后完成。

答案 2 :(得分:0)

这确实很棘手,但是我找到了解决此问题的可行方法。据我所知,我是从聊天应用程序获得的,该应用程序的消息气泡大小是动态的。

我们在这里:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
                    sizeForItemAt indexPath: IndexPath) -> CGSize {

    // Minimum size
    let frame = CGRect(x: 0, y: 0, width: view.frame.width - 30, height: 0)
    let cell = MoonCollectionViewCell()

    // Fill it with the content it will have in the actual cell,
    // cell.content is just an example
    let cell.content = items[indexPath.item]
    cell.layoutIfNeeded()

    // Define the maximum size it can be
    let targetSize = CGSize(width: view.frame.width - 30, height: 240)
    let estimatedSize = cell.systemLayoutSizeFittingSize(tagetSize)

    return CGSize(width: view.frame.width - 30, height: estimatedSize.height)
}

它的基本作用是定义最小帧和目标尺寸。然后,通过调用systemLayoutSizeFittingSize,它将单元格的大小调整为最佳大小,但不大于targetSize。

根据您的需要调整代码,但这应该可行。