Final project in github: github link
I recently was working with two scrollviews in a screen. One scrollview was the subview of other scrollview. And I needed the parallax scrolling effect i.e. when one scrollview scrolls, next scrollView must scroll and some part of outer scrollview should stick at the top of visible area of View.
Below is the view controller scene from storyboard:
We need to design our storyboard first, design is also an important part of this solution. Design and constraints are given as you think is necessary to make view appear as above.
While giving height constraint for tableView (from now it will be called child scrollView), give height equal to main view of view controller.
- Click [ctrl + click] on child scrollView and drag to main View and release and select Equal Heights.
- Select child scrollView from view hierarchy
- Go to Size inspector on left side (utilities panel)
- Now, scroll down to section constraints in Size inspector and select Height constraint that you just gave in no 1.
- Now, click edit on that constraint and give negative constant which is equal to height of the menu (view portion that is to stick on top). In my case its 75 pt. so I give constant equal to -75. #Sometimes, -75 won’t be sufficient to get expected because of status bar or navigation bar on View. You need to manually decrease the value so that you get expected output. In my case, status bar gave little problem so, menu went more up after scrolled on completion. So I, changed constraint constant to -95.
Now, the coding part.
- Create IBOutlet for both child scrollView and parent scrollView in ViewController
@IBOutlet weak var parentScrollView: UIScrollView! @IBOutlet weak var tableView: UITableView!
I have used tableView as childScrollView.
- Assign delegate of both scrollView to self in viewDidLoad
parentScrollView.delegate = self tableView.delegate = self
since, UITableViewDelegate also confirms UIScrollViewDelegate assigning delegate of tableView makes ViewController be delegate of tableView’s scroll events.
- Declare two instance variables for the class
var goingUp: Bool? //to track is scrollView is going up or down var childScrollingDownDueToParent = false // track if child scrollView is scrolling due to scroll in parent or itself
- Implement UIScrollViewDelegate
func scrollViewDidScroll(scrollView: UIScrollView) { // 1: determining whether scrollview is scrolling up or down goingUp = scrollView.panGestureRecognizer.translationInView(scrollView).y < 0 // 2: maximum contentOffset y that parent scrollView can have let parentViewMaxContentYOffset = parentScrollView.contentSize.height - parentScrollView.frame.height // 3: if scrollView is going upwards if goingUp! { // 4: if scrollView is a child scrollView if scrollView == childScrollView { // 5: if parent scroll view is't scrolled maximum (i.e. menu isn't sticked on top yet) if parentScrollView.contentOffset.y < parentViewMaxContentYOffset && !childScrollingDownDueToParent { // 6: change parent scrollView contentOffset y which is equal to minimum between maximum y offset that parent scrollView can have and sum of parentScrollView's content's y offset and child's y content offset. Because, we don't want parent scrollView go above sticked menu. // Scroll parent scrollview upwards as much as child scrollView is scrolled parentScrollView.contentOffset.y = min(parentScrollView.contentOffset.y + childScrollView.contentOffset.y, parentViewMaxContentYOffset) // 7: change child scrollView's content's y offset to 0 because we are scrolling parent scrollView instead with same content offset change. childScrollView.contentOffset.y = 0 } } } // 8: Scrollview is going downwards else { if scrollView == childScrollView { // 9: when child view scrolls down. if childScrollView is scrolled to y offset 0 (child scrollView is completely scrolled down) then scroll parent scrollview instead // if childScrollView's content's y offset is less than 0 and parent's content's y offset is greater than 0 if childScrollView.contentOffset.y < 0 && parentScrollView.contentOffset.y > 0 { // 10: set parent scrollView's content's y offset to be the maximum between 0 and difference of parentScrollView's content's y offset and absolute value of childScrollView's content's y offset // we don't want parent to scroll more that 0 i.e. more downwards so we use max of 0. parentScrollView.contentOffset.y = max(parentScrollView.contentOffset.y - abs(childScrollView.contentOffset.y), 0) } } // 11: if downward scrolling view is parent scrollView if scrollView == parentScrollView { // 12: if child scrollView's content's y offset is greater than 0. i.e. child is scrolled up and content is hiding up // and parent scrollView's content's y offset is less than parentView's maximum y offset // i.e. if child view's content is hiding up and parent scrollView is scrolled down than we need to scroll content of childScrollView first if childScrollView.contentOffset.y > 0 && parentScrollView.contentOffset.y < parentViewMaxContentYOffset { // 13: set if scrolling is due to parent scrolled childScrollingDownDueToParent = true // 14: assign the scrolled offset of parent to child not exceding the offset 0 for child scroll view childScrollView.contentOffset.y = max(childScrollView.contentOffset.y - (parentViewMaxContentYOffset - parentScrollView.contentOffset.y), 0) // 15: stick parent view to top coz it's scrolled offset is assigned to child parentScrollView.contentOffset.y = parentViewMaxContentYOffset childScrollingDownDueToParent = false } } } }
Why that negative constant while designing ? Never mind. 😛
Its because, parent scrollView needs content size, so we kept on giving height constraint to all views added in scrollView. Specifically, we gave child scrollView’s height equal to height of main view so that scrollView gets content size. Now, parent scroll view will scroll maximum till our child scrollView’s frame will appear on screen. If you don’t give constant to the constraint, menu won’t stick to top. Giving constant -95 to height constraint means we are fitting a menu and child scrollview frame in screen i.e. when parent scrollView scroll’s maximum then also menu and child scrollView will appear in screen.
Why childScrollingDownDueToParent ?
If you remove it from the places we have used, we will get a glitch like when we scroll down parent scrollView. Tap on parent scrollView and pull down. Child scrollView appears to scroll to top as once. So, to remove it we use this variable to denote if child is scrolling due to parent or due to itself. If it is scrolled due to parent then we don’t set content’s y offset to be ‘0’ at number 5 comment line.
Final project in github: github link