102

I have a UIView like iPhone's Springboard. I have created it using a UIScrollView and UIButtons. I want to disable horizontal scrolling on said scrollview. I want only vertical scrolling. How do I accomplish this?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Rahul Vyas
  • 28,260
  • 49
  • 182
  • 256
  • 5
    You should change your title : you ask for horizontal in the question and vertical in the title. People find your questions later on search engines, so it still matters 2 years later :) – Julien Jun 18 '11 at 22:15
  • @RahulVyas The man above has a point. Changing the title will decrease any confusion for beginners who get here through a search engine (such as me ;) ). – SpacyRicochet Sep 12 '11 at 09:23
  • Have edited the title on the poster's behalf. (Though it is currently awaiting peer review before the edit will be visible.) – Duncan Babbage Sep 28 '11 at 12:02

15 Answers15

128

You have to set the contentSize property of the UIScrollView. For example, if your UIScrollView is 320 pixels wide (the width of the screen), then you could do this:

CGSize scrollableSize = CGSizeMake(320, myScrollableHeight);
[myScrollView setContentSize:scrollableSize];

The UIScrollView will then only scroll vertically, because it can already display everything horizontally.

KlimczakM
  • 12,576
  • 11
  • 64
  • 83
Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • 6
    Hi. This solution works really fine! Thanks for that. I tried setting the static width to 0. For me this works too. I don't know if there might be some side effects later. But if one doesn't know the size (i.e. universal code for iPhone and iPad) this works great! – JackPearse Apr 28 '11 at 10:32
  • 5
    I think better to do CGSizeMake(0, myScrollableHeight), so horizontal scrolling disabled even if not all subviews showed. – LightNight Aug 29 '13 at 16:11
  • NB Setting one of the dimensions to 0 when using paging will cause VoiceOver to announce the page number as "page 1 of 1" regardless of the correct value – josef Jan 10 '14 at 01:09
  • 1
    Instead of passing width as 320 hardcoded, we can pass like: CGSize scrollableSize = CGSizeMake(self.scrollview.frame.size.width, myScrollableHeight); [myScrollView setContentSize:scrollableSize]; – Mohit kumar Jul 06 '16 at 11:26
99

UPDATED: (After @EranMarom pointed out on his comment)

You can stop horizontal scrolling or vertical scrolling in the ScrollViewDelegate Method. Here it is how,

Stops Horizontal Scrolling:

If you want to scroll horizontally, then you need to increase the contentOffset.x. Preventing that stops the scrollview scroll in horizontal direction.

- (void)scrollViewDidScroll:(UIScrollView *)sender {
    sender.contentOffset.x = 0.0
}

Stops Vertical Scrolling:

If you want to scroll vertically, then you need to increase the contentOffset.y. Preventing that stops the scrollview scroll in vertical direction.

- (void)scrollViewDidScroll:(UIScrollView *)sender {
    sender.contentOffset.y = 0.0
}

Above code prevents the changes in x and y of a scrollview contentOffset and it leads to stop the scrolling in scrollViewDidScroll: method.

Dinesh Raja
  • 8,501
  • 5
  • 42
  • 81
  • 1
    I'm considering downvoting because this answer doesn't explain it's self you have just given code and that's it. Answer like that aren't very good. Good answer explain themselves, whilst experienced developers may understand this newer developers might not understand what's going on here. Not downvoted yet as giving the chance to improve. – Popeye Jun 09 '13 at 12:28
  • 16
    This is sort of a hack, but it worked when all else failed. So +1, but use only as a last resort. – i_am_jorf Feb 20 '14 at 01:37
  • 5
    I also use this delegate method, but I replace the body with a one-liner, e.g.: `scrollView.contentOffset = CGPointMake(0, scrollView.contentOffset.y);` to disable horizontal scrolling. I find it more readable, it's smaller, and there is no point doing the test for zero because there's virtually no overhead with code that runs this infrequently (i.e. a few tens of times per second max). – Echelon Sep 15 '14 at 13:07
  • because you're changing the contentOffset after "didscroll", not like before 'willscroll', and this is a hammer solution, but i myself didn't downvote, because 18 upvotes and the answer is 75, so it's not worthy to downvote – LolaRun Dec 09 '14 at 11:15
  • @LolaRun But the solution is all about the resetting the changed content offset in "didScroll" method. In "willScroll", there won't be any change in content offset which we need to catch. Makes sense? I understand you didn't downvote, But to help you agree with the concept. – Dinesh Raja Dec 09 '14 at 16:27
  • This approach interferes with UIScrollView's coasting and bouncing which completely changes the experience. It doesn't cleanly do the job of only restricting scrolling in a certain direction. – davew Nov 19 '15 at 23:03
  • @davew I don't get it. Do you want to prevent scrolling but bouncing? also above code doesn't interferes with other direction. – Dinesh Raja Nov 20 '15 at 07:20
  • 2
    Even easier, use `scrollView.contentOffset.y = 0.0` to stop vertical scrolling and `scrollView.contentOffset.x = 0.0` to stop horizontal scrolling, in the `scrollViewDidScroll()` delegate function. However, I believe that making sure the `scrollView.contentSize` equals the `scrollView.frame.size` is a better (correct) solution. See @danbeaulieu answer. – emem Apr 04 '16 at 13:35
  • @EranMarom Thank you for pointing out. Not sure, if you can set 'y' position alone by now. But, above code is written a long time before. We couldn't set 'x' or 'y' property in content offset directly. The other answer you pointed out is obviously correct one. But will not work if the actual content placement is more than the scrollview size and if you used autolayout to calculate the content size. – Dinesh Raja Apr 04 '16 at 15:27
  • @DineshRaja Thanks, I did have problems using the contentSize solution in some cases (perhaps in cases the content size was reset later by the auto layouts mechanism). And yes, I mainly wanted to point out you can now set the x and y separately. – emem Apr 05 '16 at 14:33
  • @i_am_jorf You are right, this is a hack. But all else does not fail. We could just set the contentSize appropriately instead of using a hack. – Zia Apr 07 '16 at 19:16
11

since iOS7 use

self.automaticallyAdjustsScrollViewInsets = NO;

//and create you page scroller with 3 pages

    self.pageView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
    [self.pageView setContentSize:CGSizeMake(self.view.frame.size.width*3, self.view.frame.size.height)];
    [self.pageView setShowsVerticalScrollIndicator:NO];
    [self.pageView setPagingEnabled:YES];
    [self.view addSubview:self.pageView];
Mirko Brunner
  • 2,204
  • 2
  • 21
  • 22
  • I don't think this will disable the scrolling behavior of a scrollview. Is it?? – Dinesh Raja Feb 12 '14 at 03:10
  • `self.collectionView.pagingEnabled = YES;` helped me to resolve problem with smooth scrolling (not dragging). The problem was in method `scrollViewWillEndDragging:` - targetContentOffset was the same as scrollView.contentOffset and page detection logic doesn't work properly. – sig May 20 '15 at 18:18
11

Swift solution

Create two outlets, one for your view and one for your scroll view:

@IBOutlet weak var myView: UIView!
@IBOutlet weak var scrollView: UIScrollView!

Then in your viewDidLayoutSubviews you can add the following code:

let scrollSize = CGSize(width: myView.frame.size.width, 
                        height: myView.frame.size.height)
scrollView.contentSize = scrollSize

What we've done is collected the height and width of the view and set the scrollViews content size to match it. This will stop your scrollview from scrolling horizontally.


More Thoughts:

CGSizeMake takes a width & height using CGFloats. You may need to use your UIScrollViews existing height for the second parameter. Which would look like this:

let scrollSize = CGSize(width: myView.frame.size.width, 
                        height: scrollView.contentSize.height)
boraseoksoon
  • 2,164
  • 1
  • 20
  • 25
Dan Beaulieu
  • 19,406
  • 19
  • 101
  • 135
9

In my case, with Swift 4.2 you can use:

Disable vertical scroll:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    scrollView.contentOffset.y = 0.0
}

Disable horizontal scroll:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    scrollView.contentOffset.x = 0.0
}
Ricardo
  • 2,086
  • 25
  • 35
5

In my case the width of the contentView was greater than the width of UIScrollView and that was the reason for unwanted horizontal scrolling. I solved it by setting the width of contentView equal to width of UIScrollView.

Hope it helps someone

Gulfam Khan
  • 1,010
  • 12
  • 23
3

You can select the view, then under Attributes Inspector uncheck User Interaction Enabled .

Coder221
  • 1,403
  • 2
  • 19
  • 35
2

Introduced in iOS 11 is a new property on UIScrollView

var contentLayoutGuide: UILayoutGuide

The documentation states that you:

Use this layout guide when you want to create Auto Layout constraints related to the content area of a scroll view.

Along with any other Autolayout constraints that you might be adding you will want to constrain the widthAnchor of the UIScrollView's contentLayoutGuide to be the same size as the "frame". You can use the frameLayoutGuide (also introduced in iOS 11) or any external width (such as your superView's.)

example:

NSLayoutConstraint.activate([
  scrollView.contentLayoutGuide.widthAnchor.constraint(equalTo: self.widthAnchor)
])

Documentation: https://developer.apple.com/documentation/uikit/uiscrollview/2865870-contentlayoutguide

JMFR
  • 799
  • 5
  • 18
1

@Gulfam Khan's answer is the correct one, I am adding imagery to help the concept get more visibility.

When we set the contentView to have equal width's with the Scroll view, if the multiplier is even slightly greater than 1:1, then we will get horizontal scrolling.

enter image description here

Here is what it produces:

enter image description here

If you do not want horizontal scrolling, you most likely do not have horizontal content that exceeds the width of the superview.

Therefore if you ensure the contentView width does not exceed the width of the scroll view, that will automatically resolve the problem as UIKit recognizes there is no horizontal content to scroll to. Like so:

enter image description here

Now you should only see vertical:

enter image description here

ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
  • Worth checking your constraints. I'm not sure why the `multiplier` ends up as a decimal number, but this fixed it for me. – skymook Aug 10 '21 at 08:39
  • 1
    I think when you set the width to be equal when dragging , we think we set it to be exactly equal but we actually set it to be nearly exactly equal and then we get 1.00003 baked in as a multiplier. It’s the imperfection of dragging getting baked in to your multiplier. – ScottyBlades Aug 10 '21 at 09:34
0

I struggled with this for some time trying unsuccessfully the various suggestions in this and other threads.

However, in another thread (not sure where) someone suggested that using a negative constraint on the UIScrollView worked for him.

So I tried various combinations of constraints with inconsistent results. What eventually worked for me was to add leading and trailing constraints of -32 to the scrollview and add an (invisible) textview with a width of 320 (and centered).

CDspace
  • 2,639
  • 18
  • 30
  • 36
PatriciaW
  • 893
  • 1
  • 12
  • 30
0

I had the tableview contentInset set in viewDidLoad (as below) that what causing the horizontal scrolling

self.tableView.contentInset = UIEdgeInsetsMake(0, 30, 0, 0);

Check if there are any tableview contentInset set for different reasons and disable it

Naishta
  • 11,885
  • 4
  • 72
  • 54
0

Try This:

CGSize scrollSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, scrollHeight);
[scrollView setContentSize: scrollSize];
Torongo
  • 1,021
  • 1
  • 7
  • 14
0

Disable horizontal scrolling by overriding contentOffset property in subclass.

override var contentOffset: CGPoint {
  get {
    return super.contentOffset
  }
  set {
    super.contentOffset = CGPoint(x: 0, y: newValue.y)
  }
}
iWheelBuy
  • 5,470
  • 2
  • 37
  • 71
-1

Once I did it replacing the UIScrollView with a UITableView with only 1 cell, it worked fine.

-1

Use this single line.

self.automaticallyAdjustsScrollViewInsets = NO;

Karthick C
  • 1,519
  • 17
  • 16