How to resize superview to fit all subviews with autolayout? - ios

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)

Related

Custom UITableViewCell with drop-down in Swift

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.

Tableheader view autolayout issue

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.

Update the preferredMaxLayoutWidth for a multiline UILabel with unknown size (Auto Layout)

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

UIScrollView Zoom Does Not Work With Autolayout

Zooming with UIScrollView using a strictly autolayout environment does not seem to work.
This is especially frustrating because the iOS 6 release notes certainly lead me to believe it should when the wrote about a "Pure Auto Layout approach" here http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html
I looked the the WWDC 2012 slides for sessions 202, 228, and 232 and didn't see an answer for this.
The only question I've seen on the internet specifically for this issue is UIScrollView zooming with Auto Layout, but it doesn't provide code of the problem and there is no answer.
This user https://stackoverflow.com/users/341994/matt has given many great responses to UIScrollView autolayout questions and even linked to code on git hub, but I haven't been able to find anything that answers this issue there.
I have attempted to boil this issue down to the absolute minimum to make it clear.
I created a new single view application with a storyboard, and made no changes in the interface builder.
I added a large picture file to the project "pic.jpg".
SVFViewController.h
#import <UIKit/UIKit.h>
#interface SVFViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic) UIImageView *imageViewPointer;
#end
SVFViewController.m
#import "SVFViewController.h"
#interface SVFViewController ()
#end
#implementation SVFViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIScrollView *scrollView = [[UIScrollView alloc] init];
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setImage:[UIImage imageNamed:#"pic.jpg"]];
[self.view addSubview:scrollView];
[scrollView addSubview:imageView];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
self.imageViewPointer = imageView;
scrollView.maximumZoomScale = 2;
scrollView.minimumZoomScale = .5;
scrollView.delegate = self;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,imageView);
NSLog(#"Current views dictionary: %#", viewsDictionary);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
}
-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return self.imageViewPointer;
}
#end
Notice I made a particular effort to make this as much like the sample code provided in the iOS 6 release notes, just doing the bare minimum to implement zooming.
So, the problem?
When you run this application and pan around in the scroll view, everything is good. But when you zoom the problem is obvious, the image flickers back and forth, and the placement of the image within the scroll view gets more wrong with every zoom.
It looks like there is battle going on for the content offset of the imageView, it seems it is being set to different values by two different things with every "zoom". (an NSLog of the content offset property of the imageView appears to confirm this).
What am I doing wrong here? Does anyone know how to property implement zooming within a UIScrollView in an purely autolayout environment. Is there an example of this anywhere out there?
Please help.
Once again, re-reading the iOS SDK 6.0 release notes I found that:
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.
Solution
Connect your subview to the outer view. In another words, to the view in which scrollview is embedded.
And applying constraints in following way I've got it work:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageView(width)]" options:0 metrics:#{#"width":#(self.imageViewPointer.image.size.width)} views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView(height)]" options:0 metrics:#{#"height":#(self.imageViewPointer.image.size.height)} views:viewsDictionary]];
The issues that occurs is the changing of location of the imageview during the zoom process. The origin location of the imageview will change to be a negative value during the zoom. I believe this is why the jerky movement occurs. As well, after the zoom is complete the imageView is still in the wrong location meaning that scrolls will appear to be offset.
If you implement -(void) scrollViewDidZoom:(UIScrollView *)scrollView and log the frame of the UIImageView during this time you can see its origin changing.
I ended up making things work out by implementing a strategy like this
And in addition changing the frame of the contentView while zooming
-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
CGRect cFrame = self.contentView.frame;
cFrame.origin = CGPointZero;
self.contentView.frame = cFrame;
}
These solutions all kinda work. Here is what I did, no hacks or subclasses required, with this setup:
[view]
[scrollView]
[container]
[imageView]
[imageView2]
In IB, hook up top, leading, bottom and trailing of scrollView to view.
Hook up top, leading, bottom and trailing of container to scrollView.
Hook up center-x and center-y of container to center-x and center-y of scrollView and mark it as remove on build time. This is only needed to silence the warnings in IB.
Implement viewForZoomingInScrollView: in the view controller, which should be scrollView's delegate, and from that method return container.
When setting the imageView's image, determine minimum zoom scale (as right now it will be displayed at the native size) and set it:
CGSize mySize = self.view.bounds.size;
CGSize imgSize = _imageView.image.size;
CGFloat scale = fminf(mySize.width / imgSize.width,
mySize.height / imgSize.height);
_scrollView.minimumZoomScale = scale;
_scrollView.zoomScale = scale;
_scrollView.maximumZoomScale = 4 * scale;
This works for me, upon setting the image zooms the scroll view to show the entire image and allows to zoom in to 4x the initial size.
Let say you have in storyboard "UIImageView" inside "UIScrollView" inside "UIView".
Link all constraints in "UIScrollView" with the view controller + the two constraints in UIView (Horizontal Space - Scroll View - View & Horizontal Space - Scroll View - View).
set the view controller AS delegate for the "UIScrollView".
Then implement this code:
#interface VC () <UIScrollViewDelegate>
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray* constraints;
#end
#implementation FamilyTreeImageVC
- (void)viewDidLoad {
[super viewDidLoad];
[self.scrollView removeConstraints:self.constraints];
}
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
CGRect cFrame = self.imageView.frame;
cFrame.origin = CGPointZero;
self.imageView.frame = cFrame;
}
I had the same problem when trying to implement zoom from a storyboarded project using only scrollView.
I fixed it by adding a separate pinch gesture recogniser. I just dragged it from the toolbox onto my scene. Then I connected it to an action I called "doPinch" that implements the zoom. I connected it to an outlet I called "pinchRecognizer" so that I could access its scale property. This seems to override the built in zoom of the scrollView and the jumpiness disappears. Maybe it does not make the same mistake with origins, or handles that more gracefully. It is very little work on top of the layout in IB.
As soon as you introduce the pinch gesture recogniser to the scene you do need both the action and viewForZoomingInScrollView methods. Miss out either and the zooming stops working.
The code in my view controller is this:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.zoomableImage;
}
- (IBAction)doPinch:(id)sender
{
NSLog(#"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}
This very basic implementation does have a side effect: when you come back to a zoomed image and zoom some more the value of scale is 1.0f so it jumps back to the original scale.
You can sort this out by introducing a property "currentScale" to track the scale and set the pinch gesture recogniser scale when you start zooming again. You need to use the state property of the gesture recogniser:
- (IBAction)doPinch:(id)sender
{
NSLog(#"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
NSLog(#"Gesture recognizer state is: %d", self.pinchRecognizer.state);
switch (self.pinchRecognizer.state)
{
case 1:
NSLog(#"Zoom begins, with current scale set to: %f", self.currentScale);
[self.pinchRecognizer setScale:self.currentScale];
break;
case 3:
NSLog(#"Zoom ends, with pinch recognizer scale set to: %f", self.pinchRecognizer.scale);
self.currentScale = self.pinchRecognizer.scale;
default:
break;
}
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}
So this is what I managed to work out.
Here's the original with my changes:
#interface ScrollViewZoomTestViewController () <UIScrollViewDelegate>
#property (nonatomic, strong) UIImageView* imageViewPointer;
// These properties are new
#property (nonatomic, strong) NSMutableArray* imageViewConstraints;
#property (nonatomic) BOOL imageViewConstraintsNeedUpdating;
#property (nonatomic, strong) UIScrollView* scrollViewPointer;
#end
#implementation ScrollViewZoomTestViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIScrollView *scrollView = [[UIScrollView alloc] init];
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setImage:[UIImage imageNamed:#"pic.jpg"]];
[self.view addSubview:scrollView];
[scrollView addSubview:imageView];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
self.imageViewPointer = imageView;
// New
self.scrollViewPointer = scrollView;
scrollView.maximumZoomScale = 2;
scrollView.minimumZoomScale = .5;
scrollView.delegate = self;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
NSLog(#"Current views dictionary: %#", viewsDictionary);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
// Saving the image view width & height constraints
self.imageViewConstraints = [[NSMutableArray alloc] init];
// Constrain the image view to be the same width & height of the scroll view
[_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];
[_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];
// Add the image view constraints to the VIEW, not the scroll view
[self.view addConstraints:_imageViewConstraints];
// Flag
self.imageViewConstraintsNeedUpdating = YES;
}
So to recap here, I'm adding all of the constraints to self.view, saving the constraints set on the UIImageView in a NSMutableArray property, and setting a flag that the UIImageView constraints need updating.
These initial constraints on UIImageView work to set it up to start with. It will be the same width & height as the UIScrollView. However, this WON'T allow us to zoom the image view. It will keep it the same width / height as the scroll view. Not what we want. That's why I'm saving the constraints and setting the flag. We'll take care of that in a minute.
Now, set the view for zooming:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageViewPointer;
}
Ok, so now we need to actually allow us to zoom. I'm removing the initial UIImageView constraints and adding some new ones, this time constraining to the UIScrollView's contentSize width & height:
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
if(_imageViewConstraintsNeedUpdating)
{
// Remove the previous image view constraints
[self.view removeConstraints:_imageViewConstraints];
// Replace them with new ones, this time constraining against the `width` & `height` of the scroll view's content, not the scroll view itself
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_scrollViewPointer, _imageViewPointer);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imageViewPointer(width)]|" options:0 metrics:#{#"width" : #(_scrollViewPointer.contentSize.width)} views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imageViewPointer(height)]|" options:0 metrics:#{#"height" : #(_scrollViewPointer.contentSize.height)} views:viewsDictionary]];
self.imageViewConstraintsNeedUpdating = NO;
}
}
#end
We can't set the constraints up like this in -viewDidLoad because the image hasn't been rendered into the UIImageView yet, so UIScrollView's contentSize will be {0,0}.
This seems pretty hacky to me, but it does work, it does use pure Auto Layout, and I can't find a better way to do it. Seems to me like Apple needs to provide a better way to zoom content in a UIScrollView AND use Auto Layout constraints.

contentView not indenting in iOS 6 UITableViewCell prototype cell

I am configuring a custom UITableViewCell using a prototype cell in a Storyboard. However, all the UILabels (and other UI elements) do not seem to be added to the cell's contentView, instead being added to the UITableViewCell view directly. This creates issues when the cell is put into editing mode, as the content is not automatically shifted/indented (which it would do, if they were inside the contentView).
Is there any way to add the UI elements to the contentView when laying out the cell using Interface Builder/Storyboard/prototype cells? The only way I have found is to create everything in code and use [cell.contentView addSubView:labelOne] which wouldn't be great, as it is much easier to layout the cell graphically.
On further investigation (viewing the subview hierarchy of the cell) Interface Builder does place subviews within the cell's contentView, it just doesn't look like it.
The root cause of the issue was iOS 6 autolayout. When the cell is placed into editing mode (and indented) the contentView is also indented, so it stands to reason that all subviews within the contentView will move (indent) by virtue of being within the contentView. However, all the autolayout constraints applied by Interface Builder seem to be relative to the UITableViewCell itself, rather than the contentView. This means that even though the contentView indents, the subviews contained within do not - the constraints take charge.
For example, when I placed a UILabel into the cell (and positioned it 10 points from the left-hand side of the cell) IB automatically applied a constraint "Horizontal Space (10)". However, this constraint is relative to the UITableViewCell NOT the contentView. This means that when the cell is indented, and the contentView moves, the label stays put as it is complying with the constraint to remain 10 points from the left-hand side of the UITableViewCell.
Unfortunately (as far as I am aware) there is no way to remove these IB created constraints from within IB itself, so here is how I solved the problem.
Within the UITableViewCell subclass for the cell, I created an IBOutlet for that constraint called cellLabelHSpaceConstraint. You also need an IBOutlet for the label itself, which I called cellLabel. I then implemented the -awakeFromNib method as per below:
- (void)awakeFromNib {
// -------------------------------------------------------------------
// We need to create our own constraint which is effective against the
// contentView, so the UI elements indent when the cell is put into
// editing mode
// -------------------------------------------------------------------
// Remove the IB added horizontal constraint, as that's effective
// against the cell not the contentView
[self removeConstraint:self.cellLabelHSpaceConstraint];
// Create a dictionary to represent the view being positioned
NSDictionary *labelViewDictionary = NSDictionaryOfVariableBindings(_cellLabel);
// Create the new constraint
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-10-[_cellLabel]" options:0 metrics:nil views:labelViewDictionary];
// Add the constraint against the contentView
[self.contentView addConstraints:constraints];
}
In summary, the above will remove the horizontal spacing constraint which IB automatically added (as is effective against the UITableViewCell rather than the contentView) and we then define and add our own constraint to the contentView.
In my case, all the other UILabels in the cell were positioned based upon the position of the cellLabel so when I fixed up the constraint/positioning of this element all the others followed suit and positioned correctly. However, if you have a more complex layout then you may need to do this for other subviews as well.
As mentioned, XCode's Interface Builder is hiding the UITableViewCell's contentView. In reality, all UI elements added to the UITableViewCell are in fact subviews of the contentView.
For the moment, it IB is not doing the same magic for layout constraints, meaning that they are all expressed at UITableViewCell level.
A workaround is in a subclass's awakeFromNib to move all NSAutoLayoutConstrains from UITableViewCell to it's contentView and express them in terms of the contentView :
-(void)awakeFromNib{
[super awakeFromNib];
for(NSLayoutConstraint *cellConstraint in self.constraints){
[self removeConstraint:cellConstraint];
id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
NSLayoutConstraint* contentViewConstraint =
[NSLayoutConstraint constraintWithItem:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
Here is a subclass, based on other answers ideas, I'm going to base my custom cells on:
#interface FixedTableViewCell ()
- (void)initFixedTableViewCell;
#end
#interface FixedTableViewCell : UITableViewCell
#end
#implementation FixedTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (nil != (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self initFixedTableViewCell];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self initFixedTableViewCell];
}
- (void)initFixedTableViewCell {
for (NSInteger i = self.constraints.count - 1; i >= 0; i--) {
NSLayoutConstraint *constraint = [self.constraints objectAtIndex:i];
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
BOOL shouldMoveToContentView = YES;
if ([firstItem isDescendantOfView:self.contentView]) {
if (NO == [secondItem isDescendantOfView:self.contentView]) {
secondItem = self.contentView;
}
}
else if ([secondItem isDescendantOfView:self.contentView]) {
if (NO == [firstItem isDescendantOfView:self.contentView]) {
firstItem = self.contentView;
}
}
else {
shouldMoveToContentView = NO;
}
if (shouldMoveToContentView) {
[self removeConstraint:constraint];
NSLayoutConstraint *contentViewConstraint = [NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
}
#end
An alternative to subclassing is to revise the constraints in cellForRowAtIndexPath.
Embed all the content of the cell inside a container view. Then point the leading and trailing constraints to the cell.contentView rather than the table view cell.
UIView *containerView = [cell viewWithTag:999];
UIView *contentView = [cell contentView];
//remove existing leading and trailing constraints
for(NSLayoutConstraint *c in [cell constraints]){
if(c.firstItem==containerView && (c.firstAttribute==NSLayoutAttributeLeading || c.firstAttribute==NSLayoutAttributeTrailing)){
[cell removeConstraint:c];
}
}
NSLayoutConstraint *trailing = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeLeading
multiplier:1
constant:0];
[cell addConstraint:trailing];
[cell addConstraint:leading];
I think this is fixed in iOS 7 beta 3 making the workarounds unnecessary from that point on (but probably harmless as in most cases they will become empty operations).
Based on the code by Skoota (I am a beginner, don't know much of what you did, but excellent work) my suggestion is to put all your stuff in an edge-to-edge container view and add the following:
In the cell's header file, I have the following IBOutlets:
#property (weak, nonatomic) IBOutlet UIView *container;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftConstrain;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightConstrain;
In the implementation file, I have the following in awakeFromNib:
// Remove the IB added horizontal constraint, as that's effective gainst the cell not the contentView
[self removeConstraint:self.leftConstrain];
[self removeConstraint:self.rightConstrain];
// Create a dictionary to represent the view being positioned
NSDictionary *containerViewDictionary = NSDictionaryOfVariableBindings(_container);
// Create the new left constraint (0 spacing because of the edge-to-edge view 'container')
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[_container]" options:0 metrics:nil views:containerViewDictionary];
// Add the left constraint against the contentView
[self.contentView addConstraints:constraints];
// Create the new constraint right (will fix the 'Delete' button as well)
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[_container]-0-|" options:0 metrics:nil views:containerViewDictionary];
// Add the right constraint against the contentView
[self.contentView addConstraints:constraints];
Again, the above was made possible by Skoota. Thanks!!! Al credits go to him.

Resources