UICollectionViewCells在具有恒定高度约束的ScrollView中初始化

时间:2018-05-30 09:22:19

标签: ios swift uitableview uicollectionview

在我的ViewController我有一个UIScrollView,其顶部和下方有一些元素,我有一个UICollectionView,每行有2个项目。 UICollectionView的底部约束连接到滚动视图的底部。所以我想要一个滚动条,在这种情况下,我要计算UICollectionView的高度,例如。如果我有11个项目的高度(我的UICollectionView高度约束)应该是项目的6个高度+间距。这很完美。但我担心的是因为我从我的本地数据库Realm加载图像,我想做延迟加载,但没有重复使用单元格,因为它们第一次被初始化并填充数据和尽管上下滚动,它们不会被重复使用,它们总是在记忆中。因此,有时当我加载其中50多个时,我会收到内存警告,有时应用程序会崩溃。我希望这是因为UICollectionView高度的方法我曾经有过1个全局滚动条。

所以我担心的是,如果滚动条和恒定高度约束可以使用该功能但是物品/单元是否可以重复使用?

3 个答案:

答案 0 :(得分:1)

由于要将集合视图扩展到最大高度,因此基本上已禁用了可通过可重复使用的单元格提供的所有优化。 UITableViewUICollectionView在决定要重用的单元格时将始终引用各自的界限,据我所知,没有办法将此任务委托给外部滚动视图。

根据单元格的内容,您可以尝试使用UICollectionViewLayout查询layoutAttributesForElementsInRect:的可见索引路径,并在外部滚动视图滚动它们后为这些单元格加载占用大量内存的内容进入视野。您可以为此设置外部滚动视图的委托属性,并通过scrollViewDidScroll进行更新。当然,一旦图像在屏幕外滚动,您还需要担心卸载图像。

答案 1 :(得分:1)

问题

正在发生的事情是您提供了UICollectionview的高度=内容大小。因此它会加载内存中的每个单元,并且永远不会重复使用您的单元,因此您的内存会不断增加。

解决方案

您有几种方法可以修复它。

1)删除外部滚动视图,并将所有项目添加到collectionview的标题中

2)仅在colllectionview中加载缩略图

3)推荐:您已经提供了collectionview的高度及其内容大小。代替它。您应提供屏幕高度+热门商品的高度。 也就是说,如果您的顶级商品的高度为150,那么您可以将667(6s)屏幕高度添加到collectionview的是667 + 150 = 817(这是外部滚动视图的内容大小)。
通过这种方法,如果您在其中加载缩略图,则您的collectionview现在可以重用该单元格了。

第三种方法的问题是,您必须第一次滚动两次,它将滚动顶部标题(外部滚动视图),然后将开始滚动collectionview

希望对您有帮助

答案 2 :(得分:1)

UICollectionView(和UITableView)通过实例化可重复使用的单元池来优化内存。如果更改滚动位置和/或可用空间(通过更改其大小),它将从池中提取需要显示的单元格数量。 “显示”我不一定表示可见。就您而言,当将CollectionView嵌入UIScrollView并置于屏幕边界下方时,可能看不到它。当collectionView.bounds包含“ cell.frame”(甚至部分)时,将显示一个单元格。我引用“ cell.frame”是因为它实际上并不是我们正在讨论的单元实例,而是UICollectionView的内部机制来跟踪每个单元的框架。

您正在做的是给您的collectionView足够的空间(通过调整其高度)以显示您想要显示的所有内容。因此,没有在那里进行优化。

我可以想到三种选择:

1)最好只使用一个UICollectionView,而不是将UICollectionView嵌入UIScrollView中。然后,根据单元格来制作内容。您的collectionView上方的内容?使它成为一个单元格(或一堆单元格)。使其具有自己的插图和内容放置在自己的部分中。实施此选项时,我通常会定义一个枚举,其中用例是所需的内容类型。

enum ContentType {
    case header, cell(Any)
}

var data: [ContentType] = [...]

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    switch data[indexPath.row] {
        case .header:
            return headerDataSource.cell // Some object you can obtain the cell from
        case .cell(value):
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
            cell.fill(value)
            return cell
    }
}

2)从设计角度来看,此选项是个人喜好。我建议您对collectionView显示的单元格数量设置一个限制,禁用collectionView的滚动,将collectionView的高度设置为contentSize.height并添加一个“更多”单元格,该单元格将用户发送到显示其余内容并利用的UICollectionViewController优化。由于我们要限制显示的单元格数量,因此除非限制设置得太高(显然),否则它不会影响内存。当我需要显示一个画廊时,需要在其上方显示一些内容,在其下方显示一堆其他类型的单元格,并且在下方放置一些评论条目(单元格也是如此),而我又不想让用户滚动,例如,有1000个图库单元达到评论。也不想让用户再次向上滚动那1000个单元格以到达图库上方的内容。我设置了8个画廊单元格和“更多”单元格的限制,以制作一个3x3的网格,看起来很简单。

3)我认为这太过分了。获得选项1时不值得。您可以实现scrollViewDidScroll:,使用convert(_:to :)计算嵌入式collectionView的框架(根据视图控制器的视图坐标系),并将collectionView的高度约束调整为scrollView的增量框架的高度减去collectionView之前计算的框架的minY,并确保collectionView的高度不大于scrollView框架的高度。发生这种情况时,需要将额外的增量添加到collectionView的边界,以通过编程方式滚动它。鉴于我可以实施选项1,还有很多我无法解决的问题,也没有这样做的兴趣。

// This code is not working in an acceptable way but I think is a reasonable starting point.
// Also, when bouncing is disabled, a scrollView doesn't call this method when trying to scroll past the edges.
extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard !(scrollView is UICollectionView) else {return}
        let height = view.bounds.height
        let minY = collectionView.convert(collectionView.bounds, to: view).minY
        let delta = height - minY
        constraint_collection_height.constant = delta
        if delta > height {
            constraint_collection_height.constant = height
            let offset = collectionView.convert(CGPoint.zero, to: scrollView).y
            let scroll = scrollView.bounds.minY - offset
            let bounds = CGRect(origin: CGPoint(x: 0, y: scroll), size: collectionView.bounds.size)
            collectionView.bounds = bounds
        }
    }
}

编辑:一个用于回复评论的示例。

不是将collectionView嵌入单元格中来管理另一种布局。应该管理一个新的部分并在那里定义新的布局。

enum ContentType {
    case header, cell(Any), image(UIImage)
}

var data_section0: [ContentType] = [...]
var data_section1: [ContentType] = [...]

func retrieve_cell(_ collectionView: UICollectionView, _ data: [ContentType], _ row: Int) -> UICollectionViewCell {
    switch data[row] {
        case .header:
            return headerDataSource.cell // Some object you can obtain the cell from
        case .cell(value):
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
            cell.fill(value)
            return cell
        case .image(let image):
            let cell = ...
    }
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    switch indexPath.section {
        case 0: return retrieve_cell(collectionView, data_section0, indexPath.row)
        case 1: return retrieve_cell(collectionView, data_section1, indexPath.row)
        default: fatalError("There' shouldn't be a 3rd section")
    }
}

func numberOfSections(in collectionView: UICollectionView) -> Int {return 2}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    switch section {
        case 0: return data_section0.count
        case 1: return data_section1.count
        default: fatalError("There' shouldn't be a 3rd section")
    }
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    switch indexPath.section {
        case 0: return collectionView.bounds.width
        case 1: return collectionView.bounds.width / 2
        default: fatalError("There' shouldn't be a 3rd section")
    }
}