I’m trying to implement a custom top bar that behaves similarly to the iOS 11+ large title navigation bar, where the large title section of the bar collapses when scrolling down the content:
The difference is that my bar needs a custom height and also a bottom section that doesn’t collapse when scrolled. I managed to get that part working:
The bar is implemented using a UIStackView & with some non-required layout constraints, but I believe its internal implementation is not relevant. The most important thing is that the height of the bar is tied to scrollview's top contentInset
. These are driven by scrollview's contentOffset
in UIScrollViewDelegate.scrollViewDidScroll
method:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let topInset = (-scrollView.contentOffset.y).limitedBy(topBarHeightRange)
// changes both contentInset and scrollIndicatorInsets
adjustTopContentInset(topInset)
// changes top bar height
heightConstraint?.constant = topInset
adjustSmallTitleAlpha()
}
topBarHeightRange
stores the minimum and maximum bar height
One thing that I'm having a problem with is that when the user stops scrolling the scrollview, it's possible that the bar will end up in a semi-collapsed state. Again, let's look at the desired behavior:
Content offset is snapped to either the compact or expanded height, whichever is "closer". I'm trying to achieve the same in UIScrollViewDelegate.scrollViewWillEndDragging
method:
func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let targetY = targetContentOffset.pointee.y
// snaps to a "closer" value
let snappedTargetY = targetY.snappedTo([topBarHeightRange.lowerBound, topBarHeightRange.upperBound].map(-))
targetContentOffset.pointee.y = snappedTargetY
print("Snapped: \(targetY) -> \(snappedTargetY)")
}
The effect is far from perfect:
When I look at the printout it shows that the targetContentOffset
is modified correctly. However, visually in the app the content offset is snapped only to the compact height but not to the expanded height (you can observe that the large "Title" label ends up being cut in half instead of back to the "expanded" position.
I suspect this issue has something to do with changing the contentInset.top
while the user is scrolling, but I can't figure out how to fix this behavior.
It's a bit hard to explain the problem, so I hope the GIFs help. Here's the repo: https://github.com/AleksanderMaj/ScrollView
Any ideas how to make the scrollview/bar combo snap to compact/expanded height properly?