Search code examples
iosuiviewcontrolleruiinterfaceorientationautolayout

ios autolayout visual format to set a subview the same size as parent view, but offset vertically


I'm looking to implement a pull up menu style interface. I'd like the menu to be the same size as the parent view, but initially positioned so only its top 65 pixels are visible. A tap on the menu will make it animate up the screen covering most of the screen from the bottom. A further tap of the menu on anywhere that isn't an actual menu option will put it back where it came from.

I'd managed to implement this without constraints, but out of curiosity I thought I'd try and achieve the same thing with constraints (Hopefully reducing the amount of code I've got handling rotations on various device sizes etc).

Can anyone advise me on the visual format string to match sizes with the parent, but link the top to be 65 pixels above the bottom of the parent?

Also then how would I animate the change in frame position when I touch the menu?


Solution

  • There are two approaches that I might suggest:

    1. Rather than making it full size and largely off screen, it can be easier to make it 65 pixels and constrained to the bottom:

      [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[menuview]|" options:0 metrics:nil views:views]];
      self.verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[menuview(65)]|" options:0 metrics:nil views:views];
      [self.view addConstraints:self.verticalConstraints];
      

      That way, when you change orientation, it stays pinned to the bottom, 65 points tall.

      Then, when you want to animate it into place, you could:

      [self.view removeConstraints:self.verticalConstraints];
      self.verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[menuview]|" options:0 metrics:nil views:views];
      [self.view addConstraints:self.verticalConstraints];
      [UIView animateWithDuration:0.25 animations:^{
          [self.view layoutIfNeeded];
      }];
      
    2. The other approach, making it full size and largely offscreen requires you to use at least one non-VFL constraint. Again, you define the initial constraints, this time defining it to be the height of the main view (its superview), but the top constraint of the menu is to be -65 points from the bottom constraint of its superview (it's this last constraint that can't be done in VFL). Note, because I want to refer to the super view in the VFL (to define the heights to be equal), I'll use a temporary variable, mainview:

      UIView *mainview = self.view;
      NSDictionary *views = NSDictionaryOfVariableBindings(menuview, mainview);
      [mainview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[menuview]|" options:0 metrics:nil views:views]];
      [mainview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[menuview(==mainview)]" options:0 metrics:nil views:views]];
      self.verticalConstraint = [NSLayoutConstraint constraintWithItem:menuview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:mainview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-65.0];
      [mainview addConstraint:self.verticalConstraint];
      

      and then when you want to animate it into place, you get rid of that vertical constraint, and create a new one, constraining the top of the menu view to the top of its superview:

      [mainview removeConstraint:self.verticalConstraint];
      self.verticalConstraint = [NSLayoutConstraint constraintWithItem:menuview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:mainview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0];
      [mainview addConstraint:self.verticalConstraint];
      [UIView animateWithDuration:0.25 animations:^{
          [mainview layoutIfNeeded];
      }];
      

    Other techniques (such as setting the top constraint to be equal to the height of the view less 65 points) require you to intercede during rotation events. The above two techniques don't require that.