Search code examples
iosuiscrollview

I want to make effect similar to resizing top view on contacts app?


Native contacts app has interesting effect - when user tries to scroll, the scroll view pushes the top view with avatar and only when top view is in "small mode" it is scrolled up.

enter image description here

enter image description here

I was able to resize view on scrolling, via didScroll method. But problem is, content offset is also changing while i push the top vied. In native contacts, content offset changes only when top view is in "small mode"

Any suggestions, how did they made this?


Solution

  • The bounty answer is good, but it still gives somewhat choppy solution to this problem. If you want exacly like native this is your solution. I have been playing a lot lately with collection views, and have gained much more experience. One of the things that I found is that this problem can easily be solved with custom layout:

    class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {
    
        let minHeightForHeader: CGFloat = 50
        let offsetFromTop: CGFloat = 20
    
        override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    
            // get current attributes
            let attributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath)
    
            // get first section
            if let attributes = attributes, elementKind == UICollectionElementKindSectionHeader && indexPath.section == 0 {
                if let collectionView = collectionView {
    
                    // now check for content offset
                    var frame = attributes.frame
                    let yOffset = collectionView.contentOffset.y
                    if yOffset >= -offsetFromTop {
                        let calculatedHeight = frame.size.height - (yOffset + offsetFromTop)
                        let maxValue = minHeightForHeader > calculatedHeight ? minHeightForHeader : calculatedHeight
                        frame.size.height = maxValue
                        attributes.frame = frame
                    }
                    else {
                        frame.origin.y = offsetFromTop + yOffset
                        attributes.frame = frame
                    }
                }
                return attributes
            }
    
            // default return
            return attributes
        }
    
        override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            return true
        }
    }
    

    Just replace your default layout with this custom class and add this line somewhere in your setup method

    flowLayout.sectionHeadersPinToVisibleBounds = true
    

    Voila!

    EDIT: Just one more optional method for clipping part, when you scroll to certain part, scroll might continue or return:

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
    
            if let collectionView = collectionView {
                let yOffset = collectionView.contentOffset.y
                if yOffset + offsetFromTop >= maxHeightForHeader / 2 &&  yOffset + offsetFromTop < maxHeightForHeader && !(velocity.y < 0) || velocity.y > 0{
                    return CGPoint.init(x: 0, y: maxHeightForHeader - minHeightForHeader - offsetFromTop)
                }
                if yOffset + offsetFromTop < maxHeightForHeader / 2 &&  yOffset + offsetFromTop > 0 || velocity.y < 0 {
                    return CGPoint.init(x: 0, y: -offsetFromTop)
                }
            }
            return proposedContentOffset
        }