UICollectionView状态恢复:自定义滚动位置

时间:2015-02-03 23:01:53

标签: ios swift uicollectionview state-restoration uikit-state-preservation

我正在尝试为UICollectionView找到处理状态恢复的最佳方法,其元素可能会移动。 我的目标是确保在重新启动应用时,集合视图中上次查看的项目仍然可见,即使项目已移动。例如,项目A位于索引3处的单元格中应用程序被杀死,当应用程序重新启动时,如果模型说项目A应该显示在索引4,我希望集合视图初始化索引4处的单元格的偏移量。

我认为在我的UIDataSourceModelAssociation课程中实施UICollectionViewDataSource协议将为我处理这个问题,因为documentation州:

  

[UITableView和UICollectionView]类使用此协议的方法来确保将相同的数据对象(而不仅仅是相同的行索引)滚动到视图中并进行选择。

但是,我观察到的是,在恢复过程中实现此协议确实会影响所选单元格的indexPath(这对我的应用程序并不重要),但是它没有影响滚动位置。滚动位置(集合视图的contentOffset)始终恢复到应用程序被杀死时的位置,并且不受UICollectionViewDataSource的影响。

我确实有一个看起来像这样的解决方法。它与模型关联协议的模式基本相同,但我必须手动完成:

override func encodeRestorableStateWithCoder(coder: NSCoder) {
    let identifier = determineIdOfCurrentlyVisibleCell()
    coder.encodeObject(identifier, forKey: "visibleCellIdentifier")
}

override func decodeRestorableStateWithCoder(coder: NSCoder) {
    if let identifier = coder.decodeObjectForKey("visibleCellIdentifier") as? String {
        if let indexPath = model.indexPathForIdentifier(identifier) {
            collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .CenteredVertically, animated: false)
        }
    }
}

我是否误解了UIDataSourceModelAssociation的用法?有bug吗?是否有一种更优雅或更正确的方式让它发挥作用?

2 个答案:

答案 0 :(得分:5)

正如您已经指出的那样,UIDataSourceModelAssociation似乎无法恢复UICollectionView的可见偏移量,但仅适用于所选项目。我尝试在modelIdentifierForElementAtIndexPathindexPathForElementWithModelIdentifier上设置断点,并注意到它们仅在我选择了一个单元格后才被调用。如果我在我的应用程序后台处理之前清除了我的集合视图的所选单元格,那么modelIdentifierForElementAtIndexPath将不会被调用,但是一旦我将至少一个单元格设置为已选中。至少我可以证实你并不是唯一一个看到这种行为的人。

我认为由于UICollectionView的不同性质,创建将可见单元格滚动到正确点的行为可能并不简单,但这显然不会反映在Apple的文档中。手动将标识符编码到布局的第一个可见单元格应该是一个不错的选择。我正在做的是将集合视图的滚动偏移量包装在NSValue中并恢复:

var collectionView: UICollectionView?

// ...

override func encodeRestorableStateWithCoder(coder: NSCoder) {
    if let view = collectionView, offsetValue = NSValue(CGPoint: view.contentOffset) {
        coder.encodeObject(offsetValue, forKey: CollectionViewContentOffsetKey)
    }

    super.encodeRestorableStateWithCoder(coder)
}

override func decodeRestorableStateWithCoder(coder: NSCoder) {
    if let offsetValue = coder.decodeObjectForKey(CollectionViewContentOffsetKey) as? NSValue {
        collectionView?.setContentOffset(offsetValue.CGPointValue(), animated: false)
    }

    super.decodeRestorableStateWithCoder(coder)
}

答案 1 :(得分:0)

使用CGPoint根据@stepane的建议进行更新。

  override func encodeRestorableState(with coder: NSCoder) {
        super.encodeRestorableState(with: coder)
        coder.encode(collectionView.contentOffset, forKey: "CollectionViewContentOffset")
    }

    override func decodeRestorableState(with coder: NSCoder) {
        super.decodeRestorableState(with: coder)
        let offsetValue = coder.decodeObject(forKey: "CollectionViewOffset") as! CGPoint
        collectionView?.setContentOffset(offsetValue, animated: false)
    }