Avoid flicker when loading more on top
Adjusting content offset in the CollectionViewLayout.prepareLayout method avoids a flicker vs. the previous way we were doing it. // FREEBIE
This commit is contained in:
parent
19988a872a
commit
10ee054d0c
|
@ -36,6 +36,8 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryCe
|
|||
Logger.debug("\(logTag) deinit")
|
||||
}
|
||||
|
||||
fileprivate let mediaTileViewLayout: MediaTileViewLayout
|
||||
|
||||
init(mediaGalleryDataSource: MediaGalleryDataSource, uiDatabaseConnection: YapDatabaseConnection) {
|
||||
|
||||
self.mediaGalleryDataSource = mediaGalleryDataSource
|
||||
|
@ -51,12 +53,13 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryCe
|
|||
let availableWidth = screenWidth - CGFloat(kItemsPerRow + 1) * kInterItemSpacing
|
||||
let kItemWidth = floor(availableWidth / CGFloat(kItemsPerRow))
|
||||
|
||||
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
|
||||
let layout: MediaTileViewLayout = MediaTileViewLayout()
|
||||
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
layout.itemSize = CGSize(width: kItemWidth, height: kItemWidth)
|
||||
layout.minimumInteritemSpacing = kInterItemSpacing
|
||||
layout.minimumLineSpacing = kInterItemSpacing
|
||||
layout.sectionHeadersPinToVisibleBounds = true
|
||||
self.mediaTileViewLayout = layout
|
||||
|
||||
super.init(collectionViewLayout: layout)
|
||||
}
|
||||
|
@ -279,7 +282,7 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryCe
|
|||
}
|
||||
// MARK: MediaGalleryDelegate
|
||||
|
||||
public func didTapCell(_ cell: MediaGalleryCell, item: MediaGalleryItem) {
|
||||
fileprivate func didTapCell(_ cell: MediaGalleryCell, item: MediaGalleryItem) {
|
||||
Logger.debug("\(logTag) in \(#function)")
|
||||
self.delegate?.mediaTileViewController(self, didTapView: cell.imageView, mediaGalleryItem: item)
|
||||
}
|
||||
|
@ -287,8 +290,8 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryCe
|
|||
// MARK: Lazy Loading
|
||||
|
||||
// This should be substantially larger than one screen size so we don't have to call it
|
||||
// multiple times in a rapid succession, but not so large that loading more get's chopping
|
||||
let kMediaTileViewLoadBatchSize: UInt = 80
|
||||
// multiple times in a rapid succession, but not so large that loading get's really chopping
|
||||
let kMediaTileViewLoadBatchSize: UInt = 40
|
||||
var oldestLoadedItem: MediaGalleryItem? {
|
||||
guard let oldestDate = galleryDates.first else {
|
||||
return nil
|
||||
|
@ -350,10 +353,15 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryCe
|
|||
}
|
||||
isFetchingMoreData = true
|
||||
|
||||
let scrollDistanceToBottom = oldContentHeight - contentOffsetY
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
|
||||
// mediaTileViewLayout will adjust content offset to compensate for the change in content height so that
|
||||
// the same content is visible after the update. I considered doing something like setContentOffset in the
|
||||
// batchUpdate completion block, but it caused a distinct flicker, which I was able to avoid with the
|
||||
// `CollectionViewLayout.prepare` based approach.
|
||||
mediaTileViewLayout.isInsertingCellsToTop = true
|
||||
mediaTileViewLayout.contentSizeBeforeInsertingToTop = collectionView.contentSize
|
||||
collectionView.performBatchUpdates({
|
||||
mediaGalleryDataSource.ensureGalleryItemsLoaded(.before, item: oldestLoadedItem, amount: self.kMediaTileViewLoadBatchSize) { addedSections, addedItems in
|
||||
Logger.debug("\(self.logTag) in \(#function) insertingSections: \(addedSections) items: \(addedItems)")
|
||||
|
@ -362,12 +370,6 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryCe
|
|||
collectionView.insertItems(at: addedItems)
|
||||
}
|
||||
}, completion: { finished in
|
||||
|
||||
// Adjust content offset to affect change in content height so that the same content is visible after
|
||||
// the update.
|
||||
let newContentOffset = CGPoint(x: 0, y: collectionView.contentSize.height - scrollDistanceToBottom)
|
||||
collectionView.setContentOffset(newContentOffset, animated: false)
|
||||
|
||||
Logger.debug("\(self.logTag) in \(#function) performBatchUpdates finished: \(finished)")
|
||||
self.isFetchingMoreData = false
|
||||
CATransaction.commit()
|
||||
|
@ -424,7 +426,33 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryCe
|
|||
}
|
||||
}
|
||||
|
||||
class MediaGallerySectionHeader: UICollectionReusableView {
|
||||
// MARK: - Private Helper Classes
|
||||
|
||||
// Accomodates remaining scrolled to the same "apparent" position when new content is insterted
|
||||
// into the top of a collectionView. There are multiple ways to solve this problem, but this
|
||||
// is the only one which avoided a perceptible flicker.
|
||||
fileprivate class MediaTileViewLayout: UICollectionViewFlowLayout {
|
||||
|
||||
fileprivate var isInsertingCellsToTop: Bool = false
|
||||
fileprivate var contentSizeBeforeInsertingToTop: CGSize?
|
||||
|
||||
override public func prepare() {
|
||||
super.prepare()
|
||||
|
||||
if isInsertingCellsToTop {
|
||||
if let collectionView = collectionView, let oldContentSize = contentSizeBeforeInsertingToTop {
|
||||
let newContentSize = collectionViewContentSize
|
||||
let contentOffsetY = collectionView.contentOffset.y + (newContentSize.height - oldContentSize.height)
|
||||
let newOffset = CGPoint(x: collectionView.contentOffset.x, y: contentOffsetY)
|
||||
collectionView.setContentOffset(newOffset, animated: false)
|
||||
}
|
||||
contentSizeBeforeInsertingToTop = nil
|
||||
isInsertingCellsToTop = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class MediaGallerySectionHeader: UICollectionReusableView {
|
||||
|
||||
static let reuseIdentifier = "MediaGallerySectionHeader"
|
||||
|
||||
|
@ -486,11 +514,11 @@ class MediaGallerySectionHeader: UICollectionReusableView {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol MediaGalleryCellDelegate: class {
|
||||
fileprivate protocol MediaGalleryCellDelegate: class {
|
||||
func didTapCell(_ cell: MediaGalleryCell, item: MediaGalleryItem)
|
||||
}
|
||||
|
||||
public class MediaGalleryLoadingHeader: UICollectionViewCell {
|
||||
fileprivate class MediaGalleryLoadingHeader: UICollectionViewCell {
|
||||
|
||||
static let reuseIdentifier = "MediaGalleryLoadingHeader"
|
||||
|
||||
|
@ -519,7 +547,7 @@ public class MediaGalleryLoadingHeader: UICollectionViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
public class MediaGalleryCell: UICollectionViewCell {
|
||||
fileprivate class MediaGalleryCell: UICollectionViewCell {
|
||||
|
||||
static let reuseIdentifier = "MediaGalleryCell"
|
||||
|
||||
|
|
Loading…
Reference in New Issue