首页 > 解决方案 > 改进复杂集合视图构造中的滚动

问题描述

我正在开发一个灵活的表单组件,它基本上由一个带有集合视图的 UIViewController 组成,它可以具有该类的无限嵌套视图控制器。在当前状态下,它或多或少都可以正常工作,但不幸的是滚动不稳定,我还没有找到解决该问题的方法。也许你们中的某个人有一个好的解决方案的想法。我还尝试过缓存视图控制器,这有助于实现平滑滚动,但不幸的是,使用该解决方案,我开始出现显示问题。有时单元格的内容就消失了,我无法弄清楚原因。

这是完整项目的链接: https ://d.pr/f/0ij57m

这个项目的主要部分基本上就是这个FlexibleViewController(通过storyboard设计的)

class FlexibleViewController: UIViewController {

    var dataForDatasource = Array<Component>()
    var datasource: UICollectionViewDataSource?

    @IBOutlet var collectionView: UICollectionView! {
        didSet {
            let datasource = MyDatasource(data: dataForDatasource, parentController: self)
            self.datasource = datasource
            self.collectionView.dataSource = datasource
            self.collectionView.delegate = datasource
        }
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        self.view.setNeedsLayout()
        self.collectionView?.collectionViewLayout.invalidateLayout()
    }

    func invalidateLayout() {
        self.collectionView.collectionViewLayout.invalidateLayout()
        if let controller = self.parent as? FlexibleViewController {
            controller.invalidateLayout()
        }
    }
}

而这个数据源:

    class MyDatasource: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {

    weak var parentController: UIViewController!
    var components: Array<Component>!

    init(data: Array<Component>, parentController: UIViewController) {
        self.components = data
        self.parentController = parentController
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return components.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let component = components[indexPath.row]

        var cell: UICollectionViewCell?
        if component.subComponents.count > 0 {
            let collectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "collection", for: indexPath) as! TestCell

            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let vs = storyboard.instantiateViewController(withIdentifier: "flexi") as! FlexibleViewController
            vs.dataForDatasource = component.subComponents

            parentController.addChild(vs)
            collectionCell.addSubview(vs.view)
            vs.collectionView.backgroundColor = component.backgroundColor
            vs.didMove(toParent: parentController)
            collectionCell.controller = vs

            cell = collectionCell
        } else {
            cell = collectionView.dequeueReusableCell(withReuseIdentifier: "default", for: indexPath)
            cell!.backgroundColor = component.backgroundColor
        }

        return cell!
    }
}

extension MyDatasource: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        var sizeForItem: CGSize
        let component = components[indexPath.row]
        var width = Int(collectionView.frame.size.width)
        if component.width != -1 && component.width < width {
            width = component.width
        }

        var height: Int = 50
        if component.subComponents.count > 0 {
            let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: width, height: height), collectionViewLayout: UICollectionViewFlowLayout())
            let datasource = MyDatasource(data: component.subComponents, parentController: UIViewController())
            collectionView.dataSource = datasource
            collectionView.delegate = datasource
            collectionView.reloadData()
            height = Int(collectionView.collectionViewLayout.collectionViewContentSize.height)
            sizeForItem = CGSize(width: width, height: height)
        } else {
            sizeForItem = CGSize(width: width, height: height)
        }
        return sizeForItem

    }
}

如您所见,当组件具有多个组件时,将通过情节提要实例化具有集合视图的新控制器,这可能并不理想(尽管没有我预期的那么昂贵)。我尝试重新初始化 viewDidLoad 中的所有控制器,但正如我在介绍中所说,我遇到了该解决方案的显示问题。

单元格非常简单,看起来像这样:

class TestCell: UICollectionViewCell {
    var controller: UIViewController?

    override func prepareForReuse() {
        super.prepareForReuse()
        controller?.willMove(toParent: nil)
        controller?.removeFromParent()
        controller?.view.removeFromSuperview()
    }
}

组件类如下所示:

class Component {
    var generatedId = NSUUID().uuidString
    var identifier: String?
    var subComponents = Array<Component>()
    var width: Int! = -1
    var type: String! = "default"
    var backgroundColor: UIColor! = .white
    var isOpen: Bool = false
}

提前感谢您的帮助!:-)

标签: iosswift

解决方案


你应该看看 Apple 关于Prefetching Collection View Data的优秀文档

它包含一个 (Swift) 示例项目,并大量使用OperationQueueand NSCache

基本思想是“预取”并在显示下一个项目之前缓存它们以进行平滑滚动。向后滚动时,将从缓存中获取项目。


推荐阅读