Search code examples
iosobjective-cuiscrollviewmkmapviewautolayout

iOS programmatically fix the position of a subview within UIScrollView with Autolayout


I have a UIView that holds a UIScrollView. The UIScrollView contains an MKMapView subview and a placeholder subview right below it. I would like to pin the MKMapView to the top of the screen and allow the placeholder subview to slide over it and cover it up.

Apple says it's now possible to accomplish this with Autolayout but it doesn't seem to be working for me. The code below displays the UIScrollView and it's subviews properly, but the map still scrolls along with everything else. Am I missing something really obvious?

https://developer.apple.com/LIBRARY/ios/technotes/tn2154/_index.html

Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints between the view and a view outside the scroll view’s subtree, such as the scroll view’s superview.

UIView .m file:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

        // Create scroll view and add to view
        UIScrollView *scrollView = [[UIScrollView alloc] init];
        [scrollView setBackgroundColor: [UIColor clearColor]];
        [scrollView setShowsVerticalScrollIndicator:NO];
        [scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self addSubview:scrollView];

        // Create map view and add to scroll view
        mapView = [[MKMapView alloc] init];
        mapView.showsPointsOfInterest = NO;
        mapView.translatesAutoresizingMaskIntoConstraints = NO;
        [scrollView addSubview:mapView];

        // Create a placeholder image to scroll over map view
        UIImageView *randomPlaceholderStuff = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"stuff.png"]];
        [dividingLine setTranslatesAutoresizingMaskIntoConstraints:NO];
        [scrollView addSubview:dividingLine];


        // Layouts
        NSDictionary *viewArranging = NSDictionaryOfVariableBindings(scrollView, tourMap, randomPlaceholderStuff);

        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|"
                                                                     options:0
                                                                     metrics:0
                                                                       views:viewArranging]];

        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|"
                                                                     options:0
                                                                     metrics:0
                                                                       views:viewArranging]];

        UIView *referenceSuperView = scrollView.superview;

        [referenceSuperView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mapView(320)]|"
                                                                           options:0
                                                                           metrics:0
                                                                             views:viewArranging]];

        [referenceSuperView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[mapView(320)]"
                                                                           options:0
                                                                           metrics:0
                                                                             views:viewArranging]];

        [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-334-[randomPlaceholderStuff]|"
                                                                           options:0
                                                                           metrics:0
                                                                             views:viewArranging]];

        [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[randomPlaceholderStuff(320)]|"
                                                                           options:0
                                                                           metrics:0
                                                                             views:viewArranging]];

    }
    return self;
}

@end

Edit:

jrturton's answer was spot on. Here's the code that ended up working:

[self addConstraint:[NSLayoutConstraint constraintWithItem:mapView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];

[self addConstraint:[NSLayoutConstraint constraintWithItem:mapView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];

[self addConstraint:[NSLayoutConstraint constraintWithItem:mapView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:320.0]];

Solution

  • You can't do what you're trying with visual format language. The | character will always be interpreted as the view's direct superview, so you're not creating the constraints you think you are.

    You need to use the longer method (constraintWithItem...) to create individual constraints, where item1 is your floating view, and item2 is the scroll view's superview.