2

我不知道为什么设计可以适应其内容的单元格如此复杂。它不应该需要那么多代码,我仍然不明白为什么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)
    }

sizeForItem之前被调用过cellForItem,这就是 的原因layoutIfNeeded,因为我无法得到正确的intrinsic content size

我已按照建议删除了 XIB,并在 Storyboard 中设计了我的 UICollectionViewCell。

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

我只在 UICollectionViewCell 中添加了一个 UITableView。
我希望UICollectionViewCell根据tableView.

现在这是我的tableView

我创建了一个子类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 prototypetableView 单元格,样式是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 是一个自定义数据源,具有动态值(泛型)和观察者模式,用于在设置数据时重新加载集合/tableView。

当我启动应用程序时,我也有这个警告。

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

关于我应该如何处理这个问题的任何提示或建议?

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

如果我的 2 行中有 2 行UITableViewUICollectionView则在这个大小下并不总是相等。我真的不知道如何实现这一目标......

我应该invalideLayout吗?

4

3 回答 3

4

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

原因如下:

  1. UITableView是 的子类UIScrollView,但由于您已禁用其滚动功能,因此不需要UIScrollView.
  2. UITableView主要用于重用单元格,以提高性能并使代码更有条理。但是由于您将其设置为与其内容大小一样大,因此您的任何单元格都不会被重用,因此UITableView不会利用 of 的任何功能。

因此,实际上您不需要也不应该使用其中的任何一个UITableViewUIScrollView内部来UICollectionViewCell满足您的要求。

如果您同意上述部分,以下是我们实践中的一些经验教训:

  1. 我们总是将大部分底层视图和代码逻辑(主要是数据组装)移动到UIView基于自定义视图中,而不是直接放入UITableViewCell或放入UICollectionViewCell。然后将其添加到UITableViewCellorUICollectionViewCellcontentView设置约束。通过这种结构,我们可以在更多的场景中复用我们的自定义视图。
  2. 对于与您类似的要求,我们将创建一个工厂类来创建“行”,类似于您为您创建“单元格”的方式UITableView,将它们添加到一个垂直UIStackView,创建约束决定UIStackView的宽度。自动布局将处理其余的事情。
  3. 在你的使用中UICollectionViewCell,计算想要的高度,在preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes)你的单元格的内部,你可以contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)用来计算高度,做一些检查并返回。UICollectionView另外,请记住在更改宽度时使布局无效。
于 2018-12-12T11:03:34.840 回答
2

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

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

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

然后你必须确保单元格中的约束设置为可以自行调整大小;即自动布局可以计算单元格的宽度和高度。您正在提供intrinsicContentSizetableView 的一个,并且它从所有四个端都被其超级视图包裹,所以这应该没问题。

一旦你设置了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 上,单元格自动调整大小似乎已损坏,请参见此处在 iOS 12 中,UICollectionView 布局单元格何时使用笔尖中的自动布局

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

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

希望这可以帮助。

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

于 2018-12-11T08:22:30.057 回答
1

这确实很棘手,但我找到了解决这个问题的工作方法。据我所知,我是从一个聊天应用程序中得到的,其中消息气泡的大小是动态的。

开始了:

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。

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

于 2018-12-10T14:16:56.923 回答