13
votes

I have a UIScrollView with pagingEnabled. Added 5 UIStackView to subview of UIScrollView and also had one UISegmentedControl. I want to show currently selected segment and also want to disable horizontal scrolling but the view will able to scroll vertically.

let take an example:

  • Let selected page index = 0
  • User not able to scroll or drag horizontally to navigate to next page.
  • The user can scroll vertically
  • Now User selected page index = 1 in UISegmentedControl
  • 2nd page will be visible
  • Now user not able to scroll or drag to 0 and 2 index

How can I achieve this functionality using UIScrollView

Tried logic:

- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];

CGFloat xOff = self.scrollView.frame.size.width * self.currentPage;
[self.scrollView setContentSize:CGSizeMake(xOff, self.scrollView.contentSize.height)];
//[self.scrollView setContentOffset:CGPointMake(xOff, 0) animated:true];

}

but using this UIScrollView after changing page When I scroll again go to 0 page.

3
Calculate the Y positions of the selected page and set the contentOffSet of the scroll view to that position. As you have done in the commented line but here X will be always 0 just calculate Y properly.Nisar Ahmad
but my problem is that how can I disable horizontal scrolling using my approach I am not able to disable it. Also X will not always 0 let width will be 355 and selected page is 1 so X will be 355?iamVishal16
Can you please elaborate more I am not getting what are you trying to say? Please put an answer for better explanation. It will help me better.iamVishal16
stackoverflow.com/users/2227743/moritz Why did you remove the Swift tag. I know both the language very well so I don't think it's a language specific question.iamVishal16
The explained functionality can be implemented using UIPageViewController and UIViewController having UIScrollView as a subview. If you are implementation is not restricted to the UIScrollView only I can provide you solution.Shubham Daramwar

3 Answers

8
votes

You can use UICollectionView with paging enabled and inside you can implement UIScrollView with vertical scroll enable. Disable the UICollectionView scroll and change the index collection cell on click using below code:

accepted New, Edited Answer:

Add this in viewDidLayoutSubviews

SWIFT

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let indexPath = IndexPath(item: 12, section: 0)
    self.collectionView.scrollToItem(at: indexPath, at: [ .centeredHorizontally], animated: true)
}

Normally, .centerVertically is the case

Objective-C

-(void)viewDidLayoutSubviews {
   [super viewDidLayoutSubviews];
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:12 inSection:0];
   [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
4
votes

I am using this solution from my own question

In segmentDidChange() method

[self.scrollView.subviews makeObjectsPerformSelector: @selector(removeFromSuperview)];
self.scrollView.showsVerticalScrollIndicator = false;
self.scrollView.showsHorizontalScrollIndicator = false;

self.arrSubViews = [NSMutableArray array];

// Loading view's from xib's
// Now
UIView *containerView = [[UIView alloc]initWithFrame:self.scrollView.frame];
    containerView.translatesAutoresizingMaskIntoConstraints = false;

[self prepareStackViewforPage:index withContainerView:containerView];

Now preparing stackView for selected index

- (void)prepareStackViewforPage:(NSInteger)index withContainerView:(UIView *)containerView {

  [self.scrollView addSubview:containerView];

  [self applyConstraintsToParent:self.scrollView andSubView:containerView];


  UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:self.arrSubViews];
  [stackView setFrame:self.scrollView.frame];
  stackView.translatesAutoresizingMaskIntoConstraints = false;
  stackView.axis = UILayoutConstraintAxisVertical;
  stackView.spacing = 0;
  stackView.distribution = UIStackViewDistributionFill;
  stackView.alignment = UIStackViewAlignmentFill;
  [containerView addSubview:stackView];

  [self applyConstraintsToParent:containerView andSubView:stackView];

  [self.view updateConstraintsIfNeeded];
  [self.view layoutIfNeeded];
  [self.view layoutSubviews];
}

In applyConstraintsToParent:andSubView: method

- (void)applyConstraintsToParent:(UIView *)parentView andSubView:(UIView *)subView {
//constraints

NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
[parentView addConstraint:leading];

NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0];
[parentView addConstraint:trailing];

NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
[parentView addConstraint:top];

NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-8];
[parentView addConstraint:bottom];

NSLayoutConstraint *equalWidth = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0];
[parentView addConstraint:equalWidth];

leading.active = true;
trailing.active = true;
top.active = true;
bottom.active = true;
equalWidth.active = true;
}

Now it's just working fine but now I have following issues

  • View's added via xib height not changing dynamically

I have raised a question for this issue on UIStackView: Loading views from xib and updating height constraint of subView did not reflect any changes?

But this logic working fine in storyboard?

Storyboard Demo

2
votes

Following your question the answer in to set isScrollEnabled property of UIScrollView to false like following:

For Objective-C

[self.scrollView setScrollEnabled:NO];

For swift

self.scrollView.isScrollEnabled = false

As Apple documentation says about this property:

A Boolean value that determines whether scrolling is enabled.

If the value of this property is true , scrolling is enabled, and if it is false , scrolling is disabled. The default is true. When scrolling is disabled, the scroll view does not accept touch events; it forwards them up the responder chain.

So your scrollView won't accept touches, but content of your scrollView will.

And this doesn't disallow you to scroll your scrollView programmatically (which is changing contentOffset property of your scrollView). So when selected segment of your UISegmentedControl has changed you should calculate new content offset for your scrollView depending on pages size and set it, I advise you to use setContentOffset(_:animated:) method of UIScrollView to achieve scrolling.

Hope it helps.