0

I need to measure the height of a simple autolayout based VC for a given width. For example a simple UIViewController with only one label which is positioned using leading, trailing, top and bottom constraints to the VCs root view. The VC should not have a fixed size but automatically adapt to the label content.

This is only an example, of course the VC could have other content which is influences the size.

How can I calculate the VCs size for a given width and label content without adding it to the view hierarchy?

Background:

I am using a third party FormSheet control which allows to easily show any ViewController as form sheet with different styles, transitions, etc. The only downside is, that one has to specify a fixed sheet size before the VC is presented.

While this works great for VCs with "static" content / fixed sizes even a label with different texts for different languages might break the design.

Thus I am look for something like this:

ContentViewController *contentVC = [ContentViewController new];
CGRect contentBounds = [SomeClass calculateAutoLayoutHeightForView:contentVC.view withFixedWidth:500];

[ThirPartyFormSheetController presentController:contentVC withSize:contentBounds];

How can this be done?

Andrei Herford
  • 15,149
  • 16
  • 73
  • 171
  • Did you try `sizeToFit`? It is a method on `UIView` that will resize the label to its wanted size. You can get the result from the `frame` property after calling it. Not sure if you can call it on the VC root view, but it should work on the label view. – LGP Apr 08 '19 at 13:36
  • Sorry, but I do not see how this has anything to do with the question :-) This is not about auto-sizing the label but about auto-sizing the VCs view to a given width and label content. – Andrei Herford Apr 08 '19 at 13:40
  • @AndreiHerford - the "third party" control doesn't work with auto-layout? If that's the case, you want to get the height of the loaded `ContentViewController`'s view, based on a "target width" you determine on your own? Do you have auto-layout constraints set up properly in `ContentViewController`? – DonMag Apr 08 '19 at 14:55
  • @AndreiHerford - what "Third Party FormSheet" control are you using? – DonMag Apr 08 '19 at 15:55
  • I am using MZFormSheetController and as far as I know it only supports fixed form sized. However, this is only one use case where I need to programmatically calculate a layout height for a given width. Solving the problem for the form sheets would be great, but a general solution would be perfect. – Andrei Herford Apr 09 '19 at 05:48
  • Note: ViewControllers do not have height, they don't have any dimensions because they are just a class that controls views. If you have a VC you typically are adding views to it and those views have frames (height etc), usually the main UIView attached to a VC is found by using `VC.view` – Albert Renshaw Apr 09 '19 at 09:41
  • @AndreiHerford - I posted an answer with an example of using `systemLayoutSizeFittingSize:UILayoutFittingCompressedSize` to determine the height, but... I took a quick look at `MZFormSheetController` and it certainly appears that you do not *have* to set a size... it will use auto-layout to set the size automatically. – DonMag Apr 09 '19 at 12:29

2 Answers2

2

Given a width, you can use systemLayoutSizeFittingSize:UILayoutFittingCompressedSize to determine what the height will be after auto-layout does its work.

Assuming constraints in the view-to-show are set up correctly:

CGFloat w = 500.0;
[loadedView.widthAnchor constraintEqualToConstant:w].active = YES;

// caluclate the size using FittingCompressedSize
CGSize contentBounds = [loadedView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

Here is a simple example (only need to assign the ViewController class to a view controller in Storyboard... no IBOutlets needed). Lots of comments in the code should make everything clear:

//
//  ViewController.h
//  Created by Don Mag on 4/8/19.
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

//
//  ViewController.m
//  Created by Don Mag on 4/8/19.
//

#import "ViewController.h"
#import "FormViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // add a button we'll use to show the form VC
    UIButton *b = [UIButton new];
    b.translatesAutoresizingMaskIntoConstraints = NO;
    [b setTitle:@"Show Form" forState:UIControlStateNormal];
    [b setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [b setBackgroundColor:[UIColor redColor]];

    [self.view addSubview:b];

    [NSLayoutConstraint activateConstraints:
     @[
       [b.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:20.0],
       [b.widthAnchor constraintEqualToAnchor:self.view.widthAnchor multiplier:0.75],
       [b.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor]
       ]
     ];

    [b addTarget:self action:@selector(loadAndShowForm:) forControlEvents:UIControlEventTouchUpInside];

}

- (void) loadAndShowForm:(id)sender {

    // instantiate the form view controller
    FormViewController *vc = [FormViewController new];

    // get a reference to its view
    UIView *v = vc.view;

    // use auto-layout
    v.translatesAutoresizingMaskIntoConstraints = NO;

    // set the label text in the form view
    vc.topLabel.text = @"This is a bunch of text for the TOP label in the Form VC";
    vc.bottomLabel.text = @"This is a bunch of text for the BOTTOM label in the Form VC. It's enough text to cause a few lines of word-wrap, assuming we're running on an iPhone.";

    // specify a width for the form view
    // we'll use width of current view minus 60 (30-pts on each side)
    CGFloat w = self.view.frame.size.width - 60.0;
    [v.widthAnchor constraintEqualToConstant:w].active = YES;

    // caluclate the size using FittingCompressedSize
    CGSize contentBounds = [v systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    // because we set the width constraint, we now have the "compressed" height
    //[ThirdPartyFormSheetController presentController:contentVC withSize:contentBounds];

    // debugging from here down
    NSLog(@"Auto-layout resulting size: %@", [NSValue valueWithCGSize:contentBounds]);

    // set the height for the form view
    [v.heightAnchor constraintEqualToConstant:contentBounds.height].active = YES;

    // add it to the view, so we can confirm the height calculation
    [self.view addSubview:v];

    // center it on the view
    [NSLayoutConstraint activateConstraints:
     @[
       [v.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
       [v.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]
       ]
     ];

}

@end

//
//  FormViewController.h
//  Created by Don Mag on 4/8/19.
//

#import <UIKit/UIKit.h>

@interface FormViewController : UIViewController

@property (strong, nonatomic) UILabel *topLabel;
@property (strong, nonatomic) UITextField *theTextField;
@property (strong, nonatomic) UILabel *bottomLabel;

@end

//
//  FormViewController.m
//  Created by Don Mag on 4/8/19.
//

#import "FormViewController.h"

@interface FormViewController ()
@end

@implementation FormViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor redColor];

    // create a multi-line "top label"
    _topLabel = [UILabel new];
    _topLabel.backgroundColor = [UIColor cyanColor];
    _topLabel.text = @"Hello Top Label";
    _topLabel.numberOfLines = 0;

    // create a text field
    _theTextField = [UITextField new];
    _theTextField.backgroundColor = [UIColor greenColor]; // just to make it easy to see
    _theTextField.borderStyle = UITextBorderStyleRoundedRect;
    _theTextField.text = @"The Text Field";

    // create a multi-line "bottom label"
    _bottomLabel = [UILabel new];
    _bottomLabel.backgroundColor = [UIColor cyanColor];
    _bottomLabel.text = @"Hello Bottom Label";
    _bottomLabel.numberOfLines = 0;

    // we're using auto-layout and constraints
    _topLabel.translatesAutoresizingMaskIntoConstraints = NO;
    _theTextField.translatesAutoresizingMaskIntoConstraints = NO;
    _bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;

    // add to view
    [self.view addSubview:_topLabel];
    [self.view addSubview:_theTextField];
    [self.view addSubview:_bottomLabel];

    // these elements and constraints will define the height of the content
    [NSLayoutConstraint activateConstraints:
     @[

       // constrain top label leading, trailing and top to top of view, all at 20-pts
       [_topLabel.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:20.0],
       [_topLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20.0],
       [_topLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20.0],

       // constrain text field leading and trailing, and top to bottom of top label, all at 20-pts
       [_theTextField.topAnchor constraintEqualToAnchor:_topLabel.bottomAnchor constant:20.0],
       [_theTextField.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20.0],
       [_theTextField.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20.0],

       // constrain bottom label leading, trailing and top to bottom of text field, all at 20-pts
       [_bottomLabel.topAnchor constraintEqualToAnchor:_theTextField.bottomAnchor constant:20.0],
       [_bottomLabel.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-20.0],
       [_bottomLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20.0],

       // AND constrain bottom label to bottom of view at 20-pts
       [_bottomLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20.0]

       ]
     ];

}

@end

The result (adding the loaded VC's view as a subview - see the comments in the code):

enter image description here

and with more text to show the automatic height calculation:

enter image description here

If you change the amount of text for the labels (set in ViewController.m), you will see that the height is calculated correctly.

DonMag
  • 44,662
  • 5
  • 32
  • 56
0

Swift: If all you need is to calculate height on the basis of label's text, you can use this solution

https://stackoverflow.com/a/25187891/7848711