I'm loading a custom UIView's XIB file as a header view of a uitableview inside a view controller.
The file owner for the xib file is the viewcontroller. I have both the viewcontroller's & the uiview's interface declared inside the uiviewcontroller.
ViewController.h
#class ZeroStateView;
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, weak) IBOutlet CustomUITableView *tableView;
#property (nonatomic,strong) NSMutableArray *dataArray;
#property (weak, nonatomic) IBOutlet ZeroStateView *zeroStateView;
#end
#interface ZeroStateView : UIView
#property (weak, nonatomic) IBOutlet AutoLayoutLabel *titleLabel;
#property (weak, nonatomic) IBOutlet UIImageView *titleIcon;
- (void)updateView;
#end
ViewController.m
- (void)prepareHeaderViewForZeroState{
ZeroStateView *sizingView = [ZeroStateView new];
[[[NSBundle mainBundle] loadNibNamed:#"ZeroStateView" owner:self options:nil] objectAtIndex:0];
sizingView = self.zeroStateView;
[sizingView updateView];
self.tableView.tableHeaderView = sizingView;
UIView *headerView = self.tableView.tableHeaderView;
CGFloat height = [headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = headerView.frame;
headerFrame.size.height = height;
headerView.frame = headerFrame;
self.tableView.tableHeaderView = headerView;
}
#end
#implementation ZeroStateView
-(void)updateView{
self.titleIcon.alpha = 0.5;
UIFontDescriptor *titleFontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
self.titleLabel.text = #"This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. ";
}
The AutolayoutLabel class the following method overridden:
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
// For multiline label, preferredMaxLayoutWidth always matches the frame width
if (self.numberOfLines == 0 && bounds.size.width != self.preferredMaxLayoutWidth) {
self.preferredMaxLayoutWidth = self.bounds.size.width;
[self setNeedsUpdateConstraints];
}
}
The height calculated by the systemLayoutSizeFittingSize:UILayoutFittingCompressedSize returns 0. As a result I get the following view as the table header view:
When I added the actual height as below, the uilabel overflows. I'm expecting the uiview to grow as the label height grows.
headerFrame.size.height = self.sizingView.frame.size.height;
Here is the screen capture of that UIViews constraints:
What do I miss here? Can someone point me out?
Update
I created a sample project for you guys to check on whats exactly issue is.
I reimplemented what you had so far in a different way. To start, I removed the UIView in ZeroStateView.xib that the UIImageView and UILabel were embedded in. The base of the xib is already a UIView, so it is unnecessary to add another UIView to it.
Next I changed the constraints around. I don't remember exactly what constraints I changed, so I will just list them out here:
Constraints for UIImageView
Align Center X to Superview
Width = 60
Height = 56
Top Space to Superview = 37
Bottom Space to UILabel = 31
Constraints for UILabel
Left Space to Superview = 15
Right Space to Superview = 15
Bottom Space to Superview = 45
Top Space to UIImageView = 31
Onto the code. In ViewController.h, the IBOutlet wasn't doing anything as far as I could tell, so I changed that property to read #property (strong, nonatomic) ZeroStateView *zeroStateView;
Now the important changes: ViewController.m. There are two UITableViewDelegate methods that will replace prepareHeaderViewForZeroState. In viewDidLoad, initialize the zeroStateView and set the table view's delegate to self.
- (void)viewDidLoad
{
//...
// Load the view
self.zeroStateView = [[[NSBundle mainBundle] loadNibNamed:#"ZeroStateView" owner:self options:nil] firstObject];
[self.zeroStateView updateView];
self.zeroStateView.backgroundColor = [UIColor darkGrayColor];
// Set self for table view delegate for -heightForHeaderInSection: and viewForHeaderInSection:
self.dataTable.delegate = self;
}
Now that we are the table view's delegate, we get two method calls that will allow us to customize the header view and set its height appropriately.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// This will set the header view to the zero state view we made in viewDidLoad
return self.zeroStateView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
// sizeToFit describes the size that the label can fit itself into.
// So we are saying that the label can use the width of the view and infinite height.
CGSize sizeToFit = CGSizeMake(self.view.frame.size.width, MAXFLOAT);
// Then we ask the label to tell us how it can fit in that size.
// The label will respond with the width of the view and however much height it needs
// to display its text. This is the magic of how it grows vertically.
CGSize size = [self.zeroStateView.titleLabel sizeThatFits:sizeToFit];
// Add the height the label needs to the overall zero state view. This should be changed
// to the height of the UIImage + the height we just got + the whitespace above and below
// each of these views. You can handle that part.
return self.zeroStateView.frame.size.height + size.height;
}
I uploaded my changes to Dropbox here.
Just changing the size of the frame of the tableHeaderView, doesn't change it's size. You have to set it again to the tableView to force a reload.
You have to call this again self.tableView.tableHeaderView = headerView; after setting the new frame size.
As you call prepareHeaderViewForZeroState method from viewwillappear. At that point you layout is not calculate. so force layout to calculate before calling systemLayoutSizeFittingSize method to calculate height of cell. Here are the code that you need to write before calling systemLayoutSizeFittingSize.
UIView *header = self.tableView.tableHeaderView;
[header setNeedsLayout];
[header layoutIfNeeded];
Edit :
You just left 1 constraints in ZeroStateView.xib. that is Bottom Space to : Superview. kindly refer screenshot.
Output :
Here you have Updated code
Hope this help you.
I'm not sure but you could check if you have a leading,trailing,top and bottom constraints for both the UIImage and the label with reference to the superview.
Edit:
Add the width constraint before getting the systemLayoutSize
NSLayoutConstraint *tempWidthConstraint =
[NSLayoutConstraint constraintWithItem:self.contentView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:CGRectGetWidth(window.frame)];
widthConstraint.constant = tempWidthConstraint.constant;
[self.contentView addConstraint:tempWidthConstraint];
CGSize fittingSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
CGFloat height = fittingSize.height +1;
[self.contentView removeConstraint:tempWidthConstraint];
There are a few things here that are confusing to me that might be complicating things.
Why are you subclassing the label?
The constraints don't really make sense.
Why does the label have two height constraints, a constant and a ≥ constraint? Remove them both, you don't need either.
Also, your vertical space to the bottom of the container is ≥ as well. Why? As it stands, your custom view doesn't have a defined height, which could be why the fitting size returns a height of zero.
Once you resolve those issues, let me know if you have better luck.
Related
I need to change the view height in the stack view when I press the test button, but it is not working properly.
When I press the test button, I want to set the height of view 3 to 50 and the height of view5 to fill the remaining area. When I press the test button again, i want to reverse to process. How can I do that?
Thank you.
As #SeanLintern88 mentioned, the way you really should be doing this is with auto layout constraints -- you don't want to be mixing setFrame with autolayout.
IBOutlet the height constraints for View 3 and View 5. Set the View 3 height constraint as inactive to start (if you want it to look like your storyboard does currently to start), then whenever the button is pressed, check which constraint is active and flip-flop them.
#import "ViewController.h"
#interface ViewController ()
#property (strong, nullable) IBOutlet NSLayoutConstraint *view3HeightConstraint;
#property (strong, nullable) IBOutlet NSLayoutConstraint *view5HeightConstraint;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// start us out as inactive
self.view3HeightConstraint.active = NO;
}
- (IBAction)btnPressed:(id)sender {
if (self.view5HeightConstraint.active) {
// view 5 height constraint is active
// you can set the height constants directly in storyboard as well
self.view3HeightConstraint.constant = 50.0f;
self.view3HeightConstraint.active = YES;
self.view5HeightConstraint.active = NO;
} else {
// view 3 is height constraint is active
// you can set the height constants directly in storyboard as well
self.view5HeightConstraint.constant = 50.0f;
self.view5HeightConstraint.active = YES;
self.view3HeightConstraint.active = NO;
}
// animate the layoutIfNeeded so we can get a smooth animation transition
[UIView animateWithDuration:1.0f animations:^{
[self.view layoutIfNeeded];
}];
}
#end
I want to implement custom UITableViewCell with XIB. Here is the drawing. The button on the right side of the main view shows/hides additional view. I have two questions:
How can I hide the additional view? I think that one possibility would be to set height in the frame of additional UIView to zero. Are there any better options?
The buttons in additional view (in this case 1-5) should appear dynamically. There are two sets of data: one for the left side (buttons 1-3) and other one for the right side (buttons 4-5). The height for the left buttons is fixed, let's say 70px each. The height of the buttons on the right side should be adjusted so that total height of right side buttons is the same as total height on left side. How can the buttons be added dynamically considering these rules?
The buttons will be added during runtime. For example there are two arrays:
var leftButtons:[String] = ["button1label", “button2label“, "button3label"]
var rightButtons:[String] = ["button4label", "button5label"].
Let's say I add "button6label" to leftButtons later on during runtime. The size for the leftView/rightView as well as the size for the buttons inside these views should be adjusted. Again, height of each button on the left-hand size is fixed.
Firstly you should add ContainerView view on your custom UITableViewCell, Now add constraints on the ContainerView - leading , trailing, top & bottom to superView with priority 999 for all constraints`.
Now you should add two view on ContainerView, one is mainView and other is additionView.
and add constraints on the mainView - leading to superView, top to superView, trailing to superView and height constraint (let say 70).
now add textfield and show/hide button in side the mainView and apply constraints on textField and show/hide button.
textField constraints - leading to superView, top to superView, bottom to superView and Horizontal spacing between textField & show/hide button.
show/hide button constraints - top to superView, bottom to superView, trailing to superView and width constraints.
Here mainView is configured properly. So now let's configure additionView
you should add two new view in additionView, one is leftView and other is rightView & add constraints on the leftView & rightView.
leftView constraints - leading to superView, top to superView, bottom to superView , Horizontal spacing between leftView & rightView,
equal width and width constraints of leftView to rightView.
rightView constraints - trailing to superView, top to superView&bottom to superView
Here your Interface Builder Designing completed so now we need to manage the buttons on left and right View at runtime. to do this you must make a custome classs is called VerticalContainerView that will manage the buttons vertical distribution.
I have created the VerticalContainerView using the KVConstraintExtensionsMaster library to apply constraints that I have implemented.
Put below code in VerticalContainerView.h header file
#import <UIKit/UIKit.h>
#interface VerticalContainerView : UIView
-(void)configureButtonsbyNames:(NSArray<__kindof NSString *>*)names isDistribuated:(BOOL)isDistributed;
#end
Put below code in VerticalContainerView.m file
#import "VerticalContainerView.h"
#import "KVConstraintExtensionsMaster.h"
#implementation VerticalContainerView
-(void)configureButtonsbyNames:(NSArray<__kindof NSString *>*)names isDistribuated:(BOOL)isDistributed
{
/* Just Two steps to Apply\Add constraints by programatically */
/* Step 1 create & configure the view hierarchy for constraint first. */
/* Step 2 Apply the constraints */
CGFloat space = 0.0;
CGFloat height = 70.0;
UIButton *previousContentButton = nil;
NSInteger count = names.count;
for (NSInteger i = 0; i < count; i++)
{
UIButton *contentButton = [UIButton prepareNewViewForAutoLayout];
if (i&1) {
[contentButton setBackgroundColor:[UIColor greenColor]];
}else{
[contentButton setBackgroundColor:[UIColor purpleColor]];
}
[contentButton setTag:i];
[contentButton setTitle:names[i] forState:UIControlStateNormal];
[self addSubview:contentButton];
[contentButton applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:space];
if (!isDistributed) {
[contentButton applyHeightConstraint:height];
}
if (i == 0) // for first
{
[contentButton applyTopPinConstraintToSuperviewWithPadding:space];
}
else if (i == count-1) // for last
{
if (isDistributed) {
[previousContentButton applyConstraintFromSiblingViewAttribute:NSLayoutAttributeHeight toAttribute:NSLayoutAttributeHeight ofView:contentButton spacing:space];
}
[previousContentButton applyConstraintFromSiblingViewAttribute:NSLayoutAttributeBottom toAttribute:NSLayoutAttributeTop ofView:contentButton spacing:space];
[contentButton applyBottomPinConstraintToSuperviewWithPadding:space];
}
else
{
if (isDistributed) {
[previousContentButton applyConstraintFromSiblingViewAttribute:NSLayoutAttributeHeight toAttribute:NSLayoutAttributeHeight ofView:contentButton spacing:space];
}
[previousContentButton applyConstraintFromSiblingViewAttribute:NSLayoutAttributeBottom toAttribute:NSLayoutAttributeTop ofView:contentButton spacing:space];
}
previousContentButton = contentButton;
}
}
#end
Now create a custom Cell is called CustomCell & put below code in CustomCell.h header file
#import "VerticalContainerView.h"
#interface CustomCell : UITableViewCell
#property (weak, nonatomic) IBOutlet VerticalContainerView *leftVerticalContainerView;
#property (weak, nonatomic) IBOutlet VerticalContainerView *rightVerticalContainerView;
#end
Put below code in CustomCell.m file.
#import "CustomCell.h"
#implementation CustomCell
-(void)prepareForReuse
{
// here you have to remove the all the buttons from left and right veiw becuase
// Every cell can have distinct number buttons on left and right view.
for (UIView *subView in self.leftVerticalContainerView.subviews) {
[subView removeFromSuperview];
}
for (UIView *subView in self.leftVerticalContainerView.subviews) {
[subView removeFromSuperview];
}
[super prepareForReuse];
}
#end
Now changes UITableViewCell Class by our CustomCell with the help of Identity inspector editor of Interface Builder
Also changes left and right View Class by our VerticalContainerView with the help of Identity inspector editor of Interface Builder
Now connect the IBOutlet of our CusromCell for leftVerticalContainerView & rightVerticalContainerView
Put the below code in the viewDidLoad method ofyour ViewController is:
tableView.rowHeight = UITableViewAutomaticDimension;
/* any estimated height but must be more than 2, but it should be more estimated */
tableView.estimatedRowHeight = 210.0;
tableView.delegate = self;
tableView.dataSource = self;
// if you created cell from `.xib` is called CustomCell.xib,then you have to register that cell with table.
// UINib *nib = [UINib nibWithNibName:#"CustomCell" bundle:nil];
// [tableView registerNib:nib forCellReuseIdentifier:#"YouCellIdentifier"];
Now implement UITableView DataSource in your ViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section{
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"CustomCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (indexPath.row%2 == 0) {
// this is fixed height constraints
[cell.leftVerticalContainerView configureButtonsbyNames:#[#"button1",#"button2",#"button3"] isDistributed:NO];
// this is distributed height constraints according to left view
[cell.rightVerticalContainerView configureButtonsbyNames:#[#"button4",#"button5"] isDistributed:YES];
}
else{
// this is fixed height constraints
[cell.leftVerticalContainerView configureButtonsbyNames:#[#"button1",#"button2",#"button3",#"button4",#"button5"] isDistributed:NO];
// this is isDistribuated height constraints according to left view
[cell.rightVerticalContainerView configureButtonsbyNames:#[#"button6",#"button7",#"button8"] isDistributed:YES];
}
return cell;
}
You can do this using UITableViewCells with Automatic Height.
This allows you to use constraints to determine the cell's height.
To get this working:
Add the following lines to turn on Automatic Height:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0 // An approximate cell height.
Use constraints to make the cell's content push outwards so that the content determines the cell height.
Add a height constraint to the additional view with an IBOutlet in code. Set this constraint's constant property to 0, and set the .hidden property of additional view to true to hide the additional view.
For a nicer opening/closing, change the constraints constant property inside a UIView animation block.
I am trying to make a vertical progress bar in iOS that can be placed as a UIView in interface builder, and given a progress bar view class. Here are my class files:
ProgressBar.h:
#interface ProgressBar : UIView
#property (nonatomic, retain) IBOutlet UIView *barView
-(void)setBarValue:(float)value;
#end
ProgressBar.m:
-(id)initWithCoder:(NSCoder *)aDecoder {
id s = [super initWithCoder:aDecoder];
if (s) {
self.backgroundColor = [UIColor clearColor];
}
return s;
}
-(void)setBarValue:(float)val {
[self.barView setBackgroudnColor:TURQUOISE];
CGRect frame = self.barView.frame;
frame.size.height = self.barview.frame.size.height * val;
[self.barView setFrame:frame];
}
The only constraint I have on the 'barView' inside the ProgressBar element is 'align bottom edges'. The setFrame method never seems to update the bar, and even at full height, the inner self.barView isn't the same height as the ProgressBar view. The properties are all correctly linked in the Storyboard.
Any ideas?
The key issue is that, if using autolayout, you should add your constraints for this subview in IB, too. (I assume you already added constraints for the ProgressView.) And you should not change the frame of barView, but rather change its constraints and then call setNeedsLayout. The easiest way will be to add leading/trailing/top constraints of zero and then create a final height constraint. You can then add an IBOutlet for that height constraint. You can then programmatically set the constant of that constraint (and if you're going to use a multiplicative factor, it should be the product of this factor and the overall progress view, not a factor of the bar, itself), and then call setNeedsLayout.
I have a custom view class which inherits from UIView. This class has an UILabel as its subview. In the init-function of this custom view class I set up everything needed like this:
//h-file
#import <UIKit/UIKit.h>
#interface MyCustomView : UIView
#property (strong, nonatomic) UILabel *myLabel;
#end
//m-file
#implementation MyCustomView
#synthesize myLabel = _myLabel;
- (id)init
{
self = [super init];
if (self) {
_myLabel = [UILabel new];
if(_textView){
_myLabel.highlightedTextColor = [UIColor whiteColor];
_myLabel.translatesAutoresizingMaskIntoConstraints = NO;
_myLabel.lineBreakMode = NSLineBreakByWordWrapping;
_myLabel.numberOfLines = 0;
_myLabel.backgroundColor = [UIColor clearColor];
[self addSubview:_myLabel];
}
}
return self;
}
#end
I also set up a bunch of constraints to manage padding inside my custom view - furthermore there are constraints to layout multiple MyCustomView-instances for both vertical and horizontal axis as well.
To get a multilined label output I have to set the preferredMaxLayoutWidth-property of the UILabel myLabel. The width depends on the free space available. At http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html I read, that I can let Auto Layout calculate the width first and set it as preferredMaxLayoutWidth after the frame of the MyCustomView-instance (the label inside is single lined at this moment) has been set.
If I put the following function into the MyCustomView, the label still has a single line of text:
- (void)layoutSubviews
{
[super layoutSubviews];
float width = _myLabel.frame.size.width;
_myLabel.preferredMaxLayoutWidth = width;
[super layoutSubviews];
}
If I set the preferredMaxLayoutWidth to an explicit value inside the init-function, the label is multilined.
Does anybody know what I am doing wrong here?
Thanks in advance!
Without seeing all the constrains you have setup for your custom view, and the superview that contains it, it's really hard to determine the problem, I suggest you to print out all the view frames of the entire view hierarchy starting from the view controller's view at viewDidLayoutSubviews and determine if the label and its superviews have correct frame set.
I have an encountered similar issues with dynamic label size and scroll view so I created a prototype here, might be useful to you too: https://github.com/briandotnet/AutoLayoutScrollViewExperiment
My understanding of autolayout is that it takes the size of superview and base on constrains and intrinsic sizes it calculates positions of subviews.
Is there a way to reverse this process? I want to resize superview on the base of constrains and intrinsic sizes. What is the simplest way of achieving this?
I have view designed in Xcode which I use as a header for UITableView. This view includes a label and a button. Size of the label differs depending on data. Depending on constrains the label successfully pushes the button down or if there is a constrain between the button and bottom of superview the label is compressed.
I have found a few similar questions but they don’t have good and easy answers.
The correct API to use is UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.
For a normal UIView using autolayout this should just work as long as your constraints are correct. If you want to use it on a UITableViewCell (to determine row height for example) then you should call it against your cell contentView and grab the height.
Further considerations exist if you have one or more UILabel's in your view that are multiline. For these it is imperitive that the preferredMaxLayoutWidth property be set correctly such that the label provides a correct intrinsicContentSize, which will be used in systemLayoutSizeFittingSize's calculation.
EDIT: by request, adding example of height calculation for a table view cell
Using autolayout for table-cell height calculation isn't super efficient but it sure is convenient, especially if you have a cell that has a complex layout.
As I said above, if you're using a multiline UILabel it's imperative to sync the preferredMaxLayoutWidth to the label width. I use a custom UILabel subclass to do this:
#implementation TSLabel
- (void) layoutSubviews
{
[super layoutSubviews];
if ( self.numberOfLines == 0 )
{
if ( self.preferredMaxLayoutWidth != self.frame.size.width )
{
self.preferredMaxLayoutWidth = self.frame.size.width;
[self setNeedsUpdateConstraints];
}
}
}
- (CGSize) intrinsicContentSize
{
CGSize s = [super intrinsicContentSize];
if ( self.numberOfLines == 0 )
{
// found out that sometimes intrinsicContentSize is 1pt too short!
s.height += 1;
}
return s;
}
#end
Here's a contrived UITableViewController subclass demonstrating heightForRowAtIndexPath:
#import "TSTableViewController.h"
#import "TSTableViewCell.h"
#implementation TSTableViewController
- (NSString*) cellText
{
return #"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
}
#pragma mark - Table view data source
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView
{
return 1;
}
- (NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger) section
{
return 1;
}
- (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath
{
static TSTableViewCell *sizingCell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: #"TSTableViewCell"];
});
// configure the cell
sizingCell.text = self.cellText;
// force layout
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
// get the fitting size
CGSize s = [sizingCell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize];
NSLog( #"fittingSize: %#", NSStringFromCGSize( s ));
return s.height;
}
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
TSTableViewCell *cell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: #"TSTableViewCell" ];
cell.text = self.cellText;
return cell;
}
#end
A simple custom cell:
#import "TSTableViewCell.h"
#import "TSLabel.h"
#implementation TSTableViewCell
{
IBOutlet TSLabel* _label;
}
- (void) setText: (NSString *) text
{
_label.text = text;
}
#end
And, here's a picture of the constraints defined in the Storyboard. Note that there are no height/width constraints on the label - those are inferred from the label's intrinsicContentSize:
Eric Baker's comment tipped me off to the core idea that in order for a view to have its size be determined by the content placed within it, then the content placed within it must have an explicit relationship with the containing view in order to drive its height (or width) dynamically. "Add subview" does not create this relationship as you might assume. You have to choose which subview is going to drive the height and/or width of the container... most commonly whatever UI element you have placed in the lower right hand corner of your overall UI. Here's some code and inline comments to illustrate the point.
Note, this may be of particular value to those working with scroll views since it's common to design around a single content view that determines its size (and communicates this to the scroll view) dynamically based on whatever you put in it. Good luck, hope this helps somebody out there.
//
// ViewController.m
// AutoLayoutDynamicVerticalContainerHeight
//
#import "ViewController.h"
#interface ViewController ()
#property (strong, nonatomic) UIView *contentView;
#property (strong, nonatomic) UILabel *myLabel;
#property (strong, nonatomic) UILabel *myOtherLabel;
#end
#implementation ViewController
- (void)viewDidLoad
{
// INVOKE SUPER
[super viewDidLoad];
// INIT ALL REQUIRED UI ELEMENTS
self.contentView = [[UIView alloc] init];
self.myLabel = [[UILabel alloc] init];
self.myOtherLabel = [[UILabel alloc] init];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_contentView, _myLabel, _myOtherLabel);
// TURN AUTO LAYOUT ON FOR EACH ONE OF THEM
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.myLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.myOtherLabel.translatesAutoresizingMaskIntoConstraints = NO;
// ESTABLISH VIEW HIERARCHY
[self.view addSubview:self.contentView]; // View adds content view
[self.contentView addSubview:self.myLabel]; // Content view adds my label (and all other UI... what's added here drives the container height (and width))
[self.contentView addSubview:self.myOtherLabel];
// LAYOUT
// Layout CONTENT VIEW (Pinned to left, top. Note, it expects to get its vertical height (and horizontal width) dynamically based on whatever is placed within).
// Note, if you don't want horizontal width to be driven by content, just pin left AND right to superview.
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to left, no horizontal width yet
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to top, no vertical height yet
/* WHATEVER WE ADD NEXT NEEDS TO EXPLICITLY "PUSH OUT ON" THE CONTAINING CONTENT VIEW SO THAT OUR CONTENT DYNAMICALLY DETERMINES THE SIZE OF THE CONTAINING VIEW */
// ^To me this is what's weird... but okay once you understand...
// Layout MY LABEL (Anchor to upper left with default margin, width and height are dynamic based on text, font, etc (i.e. UILabel has an intrinsicContentSize))
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];
// Layout MY OTHER LABEL (Anchored by vertical space to the sibling label that comes before it)
// Note, this is the view that we are choosing to use to drive the height (and width) of our container...
// The LAST "|" character is KEY, it's what drives the WIDTH of contentView (red color)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];
// Again, the LAST "|" character is KEY, it's what drives the HEIGHT of contentView (red color)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[_myLabel]-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];
// COLOR VIEWS
self.view.backgroundColor = [UIColor purpleColor];
self.contentView.backgroundColor = [UIColor redColor];
self.myLabel.backgroundColor = [UIColor orangeColor];
self.myOtherLabel.backgroundColor = [UIColor greenColor];
// CONFIGURE VIEWS
// Configure MY LABEL
self.myLabel.text = #"HELLO WORLD\nLine 2\nLine 3, yo";
self.myLabel.numberOfLines = 0; // Let it flow
// Configure MY OTHER LABEL
self.myOtherLabel.text = #"My OTHER label... This\nis the UI element I'm\narbitrarily choosing\nto drive the width and height\nof the container (the red view)";
self.myOtherLabel.numberOfLines = 0;
self.myOtherLabel.font = [UIFont systemFontOfSize:21];
}
#end
You can do this by creating a constraint and connecting it via interface builder
See explanation: Auto_Layout_Constraints_in_Interface_Builder
raywenderlich beginning-auto-layout
AutolayoutPG Articles constraint Fundamentals
#interface ViewController : UIViewController {
IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
IBOutlet NSLayoutConstraint *topSpaceConstraint;
}
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
connect this Constraint outlet with your sub views Constraint or connect super views Constraint too and set it according to your requirements like this
self.leadingSpaceConstraint.constant = 10.0;//whatever you want to assign
I hope this clarifies it.
This can be done for a normal subview inside a larger UIView, but it doesn't work automatically for headerViews. The height of a headerView is determined by what's returned by tableView:heightForHeaderInSection: so you have to calculate the height based on the height of the UILabel plus space for the UIButton and any padding you need. You need to do something like this:
-(CGFloat)tableView:(UITableView *)tableView
heightForHeaderInSection:(NSInteger)section {
NSString *s = self.headeString[indexPath.section];
CGSize size = [s sizeWithFont:[UIFont systemFontOfSize:17]
constrainedToSize:CGSizeMake(281, CGFLOAT_MAX)
lineBreakMode:NSLineBreakByWordWrapping];
return size.height + 60;
}
Here headerString is whatever string you want to populate the UILabel, and the 281 number is the width of the UILabel (as setup in Interface Builder)