18

So in a UITableView when you have sections the section view sticks to the top until the next section overlaps it and then it replaces it on top. I want to have a similar effect, where basically I have a UIView in my UIScrollView, representing the sections UIView and when it hits the top.. I want it to stay in there and not get carried up. How do I do this? I think this needs to be done in either layoutSubviews or scrollViewDidScroll and do a manipulation on the UIVIew..

xonegirlz
  • 8,889
  • 19
  • 69
  • 127

4 Answers4

24

To create UIView in UIScrollView stick to the top when scrolled up do:

func createHeaderView(_ headerView: UIView?) {
    self.headerView = headerView
    headerViewInitialY = self.headerView.frame.origin.y
    scrollView.addSubview(self.headerView)
    scrollView.delegate = self
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let headerFrame = headerView.frame
    headerFrame.origin.y = CGFloat(max(headerViewInitialY, scrollView.contentOffset.y))
    headerView.frame = headerFrame
}
evya
  • 3,381
  • 1
  • 25
  • 28
  • 2
    Make sure that the view you're intending to catch scrolls for has a delegate set, and that delegate is where scrollViewDidScroll will live. If you let your controller implement the protocol , you can assign the delegate as 'self' in the controller like [this](http://stackoverflow.com/a/14096093) – Maya Webster Mar 18 '15 at 20:49
  • I tried this and it works fine but my view get translucent and i tried everything but not succeeded, so please can you help me the way or reason why it is happening. Its only get translucent for a time till it sticks to top – Soniya Apr 14 '16 at 10:44
11

Swift Solution based on EVYA's response:

var navigationBarOriginalOffset : CGFloat?

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    navigationBarOriginalOffset = navigationBar.frame.origin.y
}

func scrollViewDidScroll(scrollView: UIScrollView) {
    navigationBar.frame.origin.y = max(navigationBarOriginalOffset!, scrollView.contentOffset.y)
}
Scott Byrns KCL
  • 211
  • 4
  • 5
2

If I recall correctly, the 2010 WWDC ScrollView presentation discusses precisely how to keep a view in a fixed position while other elements scroll around it. Watch the video and you should have a clear-cut approach to implement.

It's essentially updating frames based on scrollViewDidScroll callbacks (although memory is a bit hazy on the finer points).

isaac
  • 4,867
  • 1
  • 21
  • 31
  • hmm.. basically you have to check if that sectioned UIView is on top and then keep it there right? My issue is how to determine if that sectioned UIView is on "top" – xonegirlz Jun 30 '12 at 16:01
  • To determine which view is on top, you can ask the superview for it's array of subviews, e.g. NSArray *subviews = [superview subviews]. Then, ask your array for the indexes of the subviews you want to compare, e.g., NSUInteger oneIndex = [subviews indexOfObject:viewOne], twoIndex = [subviews indexOfObject:viewTwo]. Once you've go the indexes for both of the subviews, simply evaluate which is greater to determine which is on top of the other. – isaac Jun 30 '12 at 17:10
  • when I say on top, not meaning one on top of each other.. but I mean which is at contentOffSet.y – xonegirlz Jun 30 '12 at 17:13
  • Knowing nothing else about your design, it's hard to say precisely how you should "know" which is on top. My thinking would be that the view controller has a sense of which is the "current" section, based on section info coming for your model, or some other basis. Trying to determine which section/header should be at the top sheerly in the scope of views (i.e., you could do it be comparing yOffsets, or rectContainsRect or similar), does not seem like an ideal strategy imo. – isaac Jun 30 '12 at 17:17
2

Evya's solution works really well, however if you use Auto Layout, you should do something like this (The Auto Layout syntax is written in Masonry, but you get the idea.):

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    //Make the header view sticky to the top.
    [self.headerView mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.scrollView.mas_top).with.offset(scrollView.contentOffset.y);
        make.left.equalTo(self.scrollView.mas_left);
        make.right.equalTo(self.scrollView.mas_right);

        make.height.equalTo(@(headerViewHeight));
    }];

    [self.scrollView bringSubviewToFront:self.headerView];
}
Enrico Susatyo
  • 19,372
  • 18
  • 95
  • 156
  • 6
    Unfortunately this forces you to recreate the layout constraints with each frame of scroll movement. Instead, what you should do is have a container contain the `UIScrollView` and make the header's top equal to the *container's* top, not the scrollview's top plus content offset. Doing it this way allows you to set up the constraints once. – Anthony Mills Jul 18 '16 at 16:11
  • 1
    @AnthonyMills thanks for the comment! Helped me understand a lot. – Kilmazing Feb 15 '19 at 17:00