Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 267
MegaManX Avatar asked Jan 30 '17 12:01

MegaManX


2 Answers

Here's a sample project of how you could do it:
https://github.com/lukaswuerzburger/resizeable-tableview-header

You simply have a view controller with a table view and a custom view (resizable header) in it. On scrollViewDidScroll you change the height constraint.

PS: The transitions between the master and detail view controller are not prefect, but maybe someone can help me with that. The change of navigationBar's backgroundImage and shadowImage doesn't really work animated when popping back to the master view controller.

like image 53
Lukas Würzburger Avatar answered Oct 23 '22 02:10

Lukas Würzburger


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
    }
like image 44
MegaManX Avatar answered Oct 23 '22 01:10

MegaManX