UICollectionView의 deleteItems에서 콘텐츠 Offset을 좋은 느낌으로 조정

13418 단어 SwiftiOS

개시하다


죄송하지만 2018년 말, UICollectionView 이야기입니다.deleteItems 등을 통해 콘텐츠 크기를 바꾸는 경우 콘텐츠 오프셋을 조정하는 방법이 이어진다.

예제


deleteItems(at:)와reloadItems(at:) 등을 통해 UICollectionView의 Item 부분을 업데이트하고 콘텐츠 Size가 변할 때 원칙적으로 콘텐츠 Offset는 변하지 않는다.
다음 그림에서 보듯이 현재 보이는 디스플레이 영역의 아래 부분을 축소할 때는 특별히 신경 쓸 필요가 없지만 위쪽이 축소(축소된 길이에 따라 달라질 수 있음)되면 이전에 보았던 요소가 디스플레이 영역을 넘어 현재의 위치가 한순간에 사라진다.

아래 그림에서 보듯이, 디스플레이 영역 이상의 부분은 축소해도 현재 위치를 잃지 않도록 content Offset을 조정하고 싶습니다.

사용자 정의 UICollectionViewLayout


deleteItems(at:)를 실행한 후 scrollToItem() 등으로 콘텐츠 오프셋을 변경하는 방법도 있지만 삭제된 애니메이션이 완성되면 이동하기 때문에 조화롭지 못함을 느낄 수 있습니다.삭제된 애니메이션에서 content Offset을 동시에 변경하려면 UICollection ViewLayout을 사용자 정의해야 합니다.

Item 업데이트 시 호출하는 방법


Item이 업데이트되면 UICollectionViewLayout의 다음 메서드가 호출됩니다.
  • prepare(forCollectionViewUpdates:)
  • targetContentOffset(forProposedContentOffset:)
  • finalLayoutAttributesForDisappearingItem(at:)
  • initialLayoutAttributesForAppearingItem(at:)
  • finalizeCollectionViewUpdates()
  • 이번 목적을 실현하려면TargetContent Offset(forProcessedContent Offset:)을 다시 쓰고 애니메이션이 완성되었을 때의content Offset를 되돌려 주십시오.

    prepare(forCollectionViewUpdates:)


    CustomFlowLayout.swift
    class CustomFlowLayout: UICollectionViewFlowLayout {
    
        var minIndexPath: IndexPath?
        var maxIndexPath: IndexPath?
    
        override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
            print("prepare(forCollectionViewUpdates:)")
            super.prepare(forCollectionViewUpdates: updateItems)
            guard
                let minIndexPath = updateItems.compactMap({ $0.indexPathBeforeUpdate ?? $0.indexPathAfterUpdate }).min(),
                let maxIndexPath = updateItems.compactMap({ $0.indexPathBeforeUpdate ?? $0.indexPathAfterUpdate }).max()
                else { return }
            print("    min index path:", minIndexPath)
            print("    max index path:", maxIndexPath)
            self.minIndexPath = minIndexPath
            self.maxIndexPath = maxIndexPath
        }
        ...
    }
    
    prepare(forCollectionViewUpdates:)에서 업데이트된 객체의 Item 메시지가 전송되었습니다.여기서 업데이트 범위를 파악하기 위해 업데이트 대상의 최소 및 최대 크기를 미리 유지합니다.

    targetContentOffset(forProposedContentOffset:)


    CustomFlowLayout.swift
        override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
            print("targetContentOffset(forProposedContentOffset:)")
            let targetContentOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
            guard let collectionView = collectionView,
                let minIndexPath = minIndexPath,
                let maxIndexPath = maxIndexPath,
                let minAttributes = layoutAttributesForItem(at: minIndexPath),
                let maxAttributes = layoutAttributesForItem(at: maxIndexPath)
                else { return targetContentOffset }
    
            let viewTop = collectionView.contentOffset.y
            let viewBottom = viewTop + collectionView.frame.size.height
            print("        view range: \(viewTop) - \(viewBottom)")
    
            let updateTop = minAttributes.frame.origin.y
            let updateBottom = maxAttributes.frame.origin.y
            print("      update range: \(updateTop) - \(updateBottom)")
    
            let currentHeight = collectionView.contentSize.height
            let newHeight = collectionViewContentSize.height
            print("    current height:", currentHeight)
            print("        new height:", newHeight)
    
            if currentHeight > newHeight,
                viewBottom > updateBottom {
                let diff = currentHeight - newHeight
                return CGPoint(x: targetContentOffset.x,
                               y: max(collectionView.contentOffset.y - diff, 0))
            }
            return targetContentOffset
        }
    

    이런 인상으로어수선하지만 영역 이상의 부분이 축소된 것으로 판단됐다.를 참고하십시오.진실로 판정된 상태에서 수축된 부분만 있다.y로 이동한 CGPoint를 반환합니다.
    참고로 업데이트 전 (현재) 콘텐츠 크기는collectionView입니다.콘텐츠 Size에서 업데이트된 콘텐츠 Size는collectionViewContent Size를 통해 각각 얻을 수 있습니다.

    finalizeCollectionViewUpdates()


    CustomFlowLayout.swift
        override func finalizeCollectionViewUpdates() {
            print("finalizeCollectionViewUpdates()")
            super.finalizeCollectionViewUpdates()
            minIndexPath = nil
            maxIndexPath = nil
        }
    
    당분간 최종 처리를 잊지 마라.

    끝맺다


    이번에 만든 프로젝트는 아래 창고에 있어요.
    https://github.com/imamurh/CollectionViewSample

    좋은 웹페이지 즐겨찾기