1

I'm trying to accomplish a scrolling movement like the iOS weather app. The following gif shows what I achieved so far.

GIF

I put a header view on the top of the main view and changed its height depending on a scroll view positioned on the main view. I then added a constraint from the top of the scroll view to the top of the main view by the header view height constant. This is the code

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let delta = scrollView.contentOffset.y - previousContentOffset

    if delta > 0 && headerView.frame.height > collapsedHeaderHeight && scrollView.contentOffset.y > 0 {
        direction = .up
        headerView.frame.size.height = max(collapsedHeaderHeight, headerView.frame.height - delta)
        scrollView.contentOffset.y -= delta
    }
    if delta < 0 && headerView.frame.height < expandedHeaderHeight && scrollView.contentOffset.y < 0 {
        direction = .down
        headerView.frame.size.height = min(headerView.frame.height - delta, expandedHeaderHeight)
        scrollView.contentOffset.y -= delta
    }

    topConstraint.constant = headerView.frame.height
    scrollView.frame.origin.y = headerView.frame.maxY
    scrollView.contentSize.height = contentView.bounds.height + headerView.bounds.height

    headerView.clipsToBounds = true
    previousContentOffset = scrollView.contentOffset.y
}

Here I resized the header view height and changed the scroll view content offset to force the scroll view not to scroll until it reaches the bottom of the collapsed header view. To move the scroll view up I changed the origin y-coordinate and modified the content size. I used the scrollViewDidEndDecelerating(_ scrollView: UIScrollView) and the scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) delegate methods to automatically bring the scroll view up or down and prevent the header view to be cut.

private func adjustScrollViewOrigin(animated: Bool) {
    func adjust(for height: CGFloat) {
        topConstraint.constant = height
        headerView.frame.size.height = height
        innerScrollView.frame.origin.y = height
    }

    switch direction {
    case .up:
        if animated {
            UIView.animate(withDuration: 0.25) { adjust(for: self.collapsedHeaderHeight) }
        } else { adjust(for: collapsedHeaderHeight) }
    case .down:
        if animated {
            UIView.animate(withDuration: 0.25) { adjust(for: self.expandedHeaderHeight) }
        } else { adjust(for: expandedHeaderHeight) }
    case .none:
        break
    }
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    adjustScrollViewInsets(animated: true)
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    adjustScrollViewInsets(animated: true)
}

Everything works fine until now. Obviously, this is not gonna work if I try to scroll from the header view. So I embedded the scroll view inside a secondary scroll view that's pinned to the main view bounds. But I can't manage to make the scroll work. Some help will be appreciated.

Dree
  • 702
  • 9
  • 29
  • This may help:- https://github.com/bryankeller/BLKFlexibleHeightBar And here is a blog post:- https://michiganlabs.com/ios/development/2016/05/31/ios-animating-uitableview-header/ – Aks Mar 22 '19 at 09:28
  • @Aks Thanks, but here the header is not scrollable and therefore it's not my use case. My header is very tall, so I have to make it scrollable along with the content below. – Dree Mar 22 '19 at 09:40
  • Is this similar to what you are looking for? https://stackoverflow.com/questions/27451041/handling-touches-for-nested-uiscrollviews-scrolling-in-the-same-direction – Aks Mar 22 '19 at 09:48

0 Answers0