Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constraints on UIScrollView with paging

I have UIScrollView which covers the whole screen, and inside it I have 3 UIView objects side-by-side, managed with paging. I'd like to make the right constraints so it would fit iPhone 6 as well.

It looks like this when dragging:

enter image description here

The constraints of the UIScrollView are working well, but how do I arrange the constraints of the views inside the UIScrollView?

thanks in Advance!!

like image 337
Liran Revivo Avatar asked Dec 25 '22 00:12

Liran Revivo


2 Answers

I had a requirement to created tutorial pages by adding views to ScrollView and I ended up in issues in arranging the views properly. I was creating Views from xib using loadNibNamed and I was not getting the correct frame when I was running the app in iPhone6 and 6plus, the frame of subviews were coming as 320 always. So I tried the pure autolayout. Since I was having a lot of views to be added, I used an array of views and then created a VFL string and then added to scrollview and it worked. Here is the sample that I created, this did the work for me

ScrollViewAutolayout

/*!

 Create an array of views that we need to load
 @param nil
 @result creates array of views and adds it to scrollview
 */
-(void)setUpViews
{
    [viewsDict setObject:contentScrollView forKey:@"parent"];
    int count = 20;//Lets layout 20 views
    for (int i=0; i<=count; i++) {
        // I am loading the view from xib.
        ContentView *contenView = [[NSBundle mainBundle] loadNibNamed:@"ContentView" owner:self options:nil][0];
        contenView.translatesAutoresizingMaskIntoConstraints = NO;
        // Layout the text and color
        [contenView layoutTheLabel];
        [contentScrollView addSubview:contenView];
        [viewsArray addObject:contenView];
    }
}
/*!
 Method to layout the childviews in the scrollview.
 @param nil
 @result layout the child views
 */
-(void)layoutViews
{
    NSMutableString *horizontalString = [NSMutableString string];
    // Keep the start of the horizontal constraint
    [horizontalString appendString:@"H:|"];
    for (int i=0; i<viewsArray.count; i++) {
        // Here I am providing the index of the array as the view name key in the dictionary
        [viewsDict setObject:viewsArray[i] forKey:[NSString stringWithFormat:@"v%d",i]];
        // Since we are having only one view vertically, then we need to add the constraint now itself. Since we need to have fullscreen, we are giving height equal to the superview.
        NSString *verticalString = [NSString stringWithFormat:@"V:|[%@(==parent)]|", [NSString stringWithFormat:@"v%d",i]];
        // add the constraint
        [contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalString options:0 metrics:nil views:viewsDict]];
        // Since we need to horizontally arrange, we construct a string, with all the views in array looped and here also we have fullwidth of superview.
        [horizontalString appendString:[NSString stringWithFormat:@"[%@(==parent)]", [NSString stringWithFormat:@"v%d",i]]];
    }
    // Close the string with the parent
    [horizontalString appendString:@"|"];
    // apply the constraint
    [contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:horizontalString options:0 metrics:nil views:viewsDict]];
}

UPDATE SWIFT 3 VERSION

Added a new repo with Swift 3 version of the above code.

ScrollViewAutoLayout

like image 28
anoop4real Avatar answered Jan 08 '23 12:01

anoop4real


Your scroll view's subviews need two sets of constraints.

  • The first set is to dictate the scrolling behavior of the scroll view (i.e. what the scroll view’s contentSize is). We use the scroll view’s contentLayoutGuide for this. Note, unlike most constraints, this does not dictate the size of the subviews, but only the relationship between these subview and the scroll view's contentSize.

  • The second set is the constraints for the size of the subviews. For this, we use the scroll view’s frameLayoutGuide.

Thus, assuming that you have three subviews, a red, green, and blue, respectively, you could do:

// Set horizontal constraints relative to scroll view contentLayoutGuide

NSLayoutConstraint.activate([
    redView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    greenView.leadingAnchor.constraint(equalTo: redView.trailingAnchor),
    blueView.leadingAnchor.constraint(equalTo: greenView.trailingAnchor),
    blueView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
])

// set constraints that are common to all three subviews

for subview in [blueView, greenView, redView] {
    NSLayoutConstraint.activate([
        // Set vertical constraints to scroll view contentLayoutGuide

        subview.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
        subview.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),

        // Set width and height of subviews relative to scroll view's frame

        subview.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
        subview.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor),
    ])
}

The above assumes that you’ve got translatesAuthresizingMaskIntoConstraints for these three subviews turned off. But hopefully the above illustrates the idea.

By the way, you can also do this all in IB (referencing the “content layout guide” and “frame layout guide”, just like above). I just did it programmatically here so you can see exactly what’s going on.


Prior to iOS 11, we didn’t have contentLayoutGuide and frameLayoutGuide. So when you set constraints between a scroll view and its subviews, it acted like contentLayoutGuide (i.e. only affected the relationship of the contentSize and the subviews) and to set the actual size of the subviews, you had to reach to the scroll view’s superview:

// Set horizontal constraints relative to scroll view contentSize

NSLayoutConstraint.activate([
    redView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
    greenView.leadingAnchor.constraint(equalTo: redView.trailingAnchor),
    blueView.leadingAnchor.constraint(equalTo: greenView.trailingAnchor),
    blueView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
])

// set constraints that are common to all three subviews

for subview in [blueView, greenView, redView] {
    NSLayoutConstraint.activate([
        // Set vertical constraints to scroll view contentSize

        subview.topAnchor.constraint(equalTo: scrollView.topAnchor),
        subview.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),

        // Set width and height of subviews relative to superview

        subview.widthAnchor.constraint(equalTo: view.widthAnchor),
        subView.heightAnchor.constraint(equalTo: view.heightAnchor),
    ])
}

By the way, Apple discusses this behavior in Technical Note TN 2154.

But if targeting iOS 11 and later, use the more intuitive contentLayoutGuide and frameLayoutGuide.

like image 146
Rob Avatar answered Jan 08 '23 14:01

Rob