iOS 8 UILabel intrinsicContentSize breaking layout constraints - ios

I have a fairly simple custom UITableViewCell subclass containing a UISwitch, UIImageView, and UILabel. All of these are subviews of the cells contentView and laid out using AutoLayout. All subviews are centered on Y and then I create a series of left and right edge constraints to arrange them horizontally. The UILabel is the rightmost item and should scale to fit between the right edge of the UIImageView to its left and the right edge of its superview. When the text it too long to fit, it should truncate. This works as expected when I test using the simulator for iOS 7.1. However, on iOS 8.1 when the text in the label is too long to fit in the allotted space, the label pushes the UIImageView to its left out of position. I have never had this issue with UILabel in the past.
Edit: I should have mentioned I get no auto layout errors when this runs.
Below are screenshots and my code:
Here is the correct behavior on iOS 7:
This what I get on iOS 8:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
self.backgroundColor = [UIColor whiteColor];
self.contentView.backgroundColor = [UIColor whiteColor];
UISwitch *filterSwitch = [[UISwitch alloc] init];
_filterSwitch = filterSwitch;
filterSwitch.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:filterSwitch];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterSwitch
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterSwitch
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:10.0]];
UIImageView *filterImageView = [[UIImageView alloc] init];
_filterImageView = filterImageView;
filterImageView.translatesAutoresizingMaskIntoConstraints = NO;
filterImageView.backgroundColor = [UIColor redColor];
filterImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:filterImageView];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:30.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:filterImageView
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:filterSwitch
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:8.0]];
UILabel *filterLabel = [[UILabel alloc] init];
_filterLabel = filterLabel;
filterLabel.translatesAutoresizingMaskIntoConstraints = NO;
filterLabel.font = [UIFont fontWithName:#"Avenir-Medium" size:14.0];
filterLabel.textColor = [UIColor colorWithWhite:0.25 alpha:1.0];
filterLabel.textAlignment = NSTextAlignmentLeft;
filterLabel.lineBreakMode = NSLineBreakByTruncatingTail;
[self.contentView addSubview:filterLabel];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterLabel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:filterImageView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:8.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterLabel
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:-10.0]];
return self;
}

Presumably, the horizontal compression resistance priorities of the switch and the label are the same. Therefore, the layout is ambiguous (which is not detected automatically, so no errors at run time). When both can't fit in the available width, it is arbitrary which will be compressed. So, you were just getting lucky (or unlucky, depending on how you look at it) on iOS 7.
Set the horizontal compression resistance of the label to be lower than that of the other two views so that is compresses first.
I just tried: if you set up the same situation — H:|-[switch]-[image(==30)]-[label]-| — in a view in IB and set the text of the label to be long enough, IB will tell you about the ambiguity problem.

Related

iOS Custom Keyboard Extension Autolayout

I'm developing a custom keyboard and i want to use only a UICollectionView as content. The CollectionView should fill (center x and y, width and height available space) of the keyboardview.
My problem is that if i rotate the device the collection view won't be resized.
I tried layout constraints, resizing the view on viewWillTransitionToSize but it does not work. Can anyone give me a hint or show me a way to get this done?
Autolayout should set translatesAutoresizingMaskIntoConstraints to NO before adding Constraint. The code below can make a UICollectionView autolayout with equal edges.
- (void)viewDidLoad {
[super viewDidLoad];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
collectionView.translatesAutoresizingMaskIntoConstraints = NO;
collectionView.backgroundColor = [UIColor redColor];
[self.view addSubview:collectionView];
//Adding layout constraint
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0]];
}
In addition, I recommend you to use Masonry to build autolayout. In your case, you just need to run the code below to make constraints.
[collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
By the way, autolayout will use more memory then calculating frame yourself. As iOS Keyboard extension has a very harsh memory usage limit (around 40mb depends on your device). If your keyboard would use many memory on process other things, I suggest you don't use autolayout.

Autolayout for UITextview inside custom UITableviewcell

I have a UITextView inside a custom UITextviewCell. I want the cell to grow in size according to the content of UITextview. I have pinned top, bottom, right, left of textview to the content view of cell. When I pre-populate textview with text, it is working fine as expected. Its not working when I enter text with keyboard. Inside my custom cell.
UITextView * textView = [[UITextView alloc]init];
[textView setTranslatesAutoresizingMaskIntoConstraints:NO];
[textView setBackgroundColor:[UIColor redColor]];
[textView setScrollEnabled:NO];
[textView setDelegate:self];
[self.contentView addSubview:textView];
NSLayoutConstraint *xConstrain = [NSLayoutConstraint constraintWithItem:textView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
NSLayoutConstraint *yConstrain = [NSLayoutConstraint constraintWithItem:textView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
NSLayoutConstraint *rightConstrain = [NSLayoutConstraint constraintWithItem:textView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];
NSLayoutConstraint *bottomConstrain = [NSLayoutConstraint constraintWithItem:textView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.contentView addConstraint:xConstrain];
[self.contentView addConstraint:yConstrain];
[self.contentView addConstraint:rightConstrain];
[self.contentView addConstraint:bottomConstrain];
In my tableview controller added following line
-(void)viewdidload
{
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 100.0f ;
}
There are a couple thing you can do here, one that comes to mind:
• As you are entering text, keep track of the height of the UITextView, as in keep an 'old height' variable and a 'new height' variable. To get the 'new height' you could use the sizeThatFits method of the UITextView with height set to for example 1000 (large enough that it won't constrain your result).
• The moment the new height is different than the old height, either smaller or larger, you reload the cell that houses the UITextView you are working on. You can event set the [cell setNeedsLayout], followed by [cell layoutIfNeeded] to make sure it resizes.
I'd imagine this should work, please let me know if it doesn't.

How to make equal space b/w UIFields using auto-layouts?

I am a beginner with iOS auto-layouts and I am adding five labels on my view controller and so for every thing is OK. (Here five labels width and heights are constant.)
My main requirement is how to make equal horizontal spacing b/w that five labels. I can set middle label and left and right corner labels and they are perfect. But I don't understand how to add second left and second right labels, and how to make equal space B/W them, as like another labels?
My requirement is exact like below image, please help me.
My code:
#import "ViewController2.h"
#interface ViewController2 ()
{
UILabel * left1;
UILabel * left2;
UILabel * middle;
UILabel * right1;
UILabel * right2;
}
#end
#implementation ViewController2
- (void)viewDidLoad {
[super viewDidLoad];
left1 = [[UILabel alloc] init];
left1.backgroundColor = [UIColor grayColor];
left1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:left1];
left2 = [[UILabel alloc] init];
left2.backgroundColor = [UIColor grayColor];
left2.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:left2];
middle = [[UILabel alloc] init];
middle.backgroundColor = [UIColor grayColor];
middle.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:middle];
right1 = [[UILabel alloc] init];
right1.backgroundColor = [UIColor grayColor];
right1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:right1];
right2 = [[UILabel alloc] init];
right2.backgroundColor = [UIColor grayColor];
right2.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:right2];
//Applying autolayouts for middle lable
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:100]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:10]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:50]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:20]];
//Appying autolayouts for left1 labe1
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:100]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:10]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:50]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:20]];
//Appying autolayouts for right1 labe1
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:100]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:-10]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:50]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:20]];
}
#end
Here is steps for you (via IB)
Create your 5 labels
Setup left and right label constraints (for left - leading, top, width, height; for right - trailing, top, width height)
Center horisontally middle label (top constraint, width, height, center horisontally)
Here is some trick - add two container views between left and middle label + middle label and right label
setup constraints for this containers ( leading and trailing constraints + top + height) This containers will be flexible depend on screen size
after adding constraints it should looks like
The last step - place other labels into green container and setup constraints as for middle label ( it should be centered in container + add top , width , height constraint )
All constraints provided at the left size, so you can easily recreate it via code, if you need
Hope this helps
So I know your question was answered, but in iOS9 there are UIStackViews which are specifically built for this situation. Here's how to use them for future reference:
Shift + click and drag to select all your labels (note you don't have to size them)
Click the StackView button on the bottom right of the screen
Then select your StackView and set Alignment to "Fill", and Distribution to "Equal Spacing"
Then with your stackview still selected click the pin icon, and put 200 at the top, 10 and 10 for the sides, and 130 for the height. Then click add 4 constraints.
Finally click the triangle icon and select update frames.
Voila! You have your layout without having to use spacers!

UITableViewController: programmatically autolayout bug on iOS 7

I want to have a UIView programmatically added to a UITableViewController which is using autolayout...
I have the following code inside of the viewDidLoad of a UITableViewController:
UIView *centerView = [[UIView alloc] init];
[centerView setTranslatesAutoresizingMaskIntoConstraints:NO];
centerView.backgroundColor = [UIColor greenColor];
[self.view addSubview:centerView];
// Width constraint, half of parent view width
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0]];
// Height constraint, half of parent view height
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0]];
// Center horizontally
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
// Center vertically
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
If I now run the app on iOS 8 all working well:
If I run it on iOS 7, the app crashes with the following error:
*** Assertion failure in -[UITableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794
If I set setTranslatesAutoresizingMaskIntoConstraints to YES, the app crashes as well with this notice: Unable to simultaneously satisfy constraints
Okay, it looks like UITableView doesn't support Autolayout on iOS 7. So we have to use autoresizing masks. My idea is to put a containerView with autoresizing mask as the tableview's subview and inside that your centerView:
Code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupBackgroundView];
}
- (void)setupBackgroundView
{
UIView *backgroundContainerView = [[UIView alloc] initWithFrame:CGRectZero];
backgroundContainerView.backgroundColor = [UIColor cyanColor];
backgroundContainerView.frame = self.tableView.bounds;
backgroundContainerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:backgroundContainerView];
UIView *centerView = [[UIView alloc] initWithFrame:CGRectZero];
centerView.translatesAutoresizingMaskIntoConstraints = NO;
centerView.backgroundColor = [UIColor greenColor];
[backgroundContainerView addSubview:centerView];
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0]];
// Height constraint, half of parent view height
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0]];
// Center horizontally
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
// Center vertically
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
}
Now centerView moves when the table view gets scrolled. If you want to have it fixed and the content hovering above, you should use backgroundView on UITableView:
- (void)setupBackgroundView
{
UIView *backgroundContainerView = [[UIView alloc] initWithFrame:CGRectZero];
backgroundContainerView.backgroundColor = [UIColor cyanColor];
UIView *centerView = [[UIView alloc] initWithFrame:CGRectZero];
centerView.translatesAutoresizingMaskIntoConstraints = NO;
centerView.backgroundColor = [UIColor greenColor];
[backgroundContainerView addSubview:centerView];
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0]];
// Height constraint, half of parent view height
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0]];
// Center horizontally
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
// Center vertically
[backgroundContainerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:backgroundContainerView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
self.tableView.backgroundView = backgroundContainerView;
}
Try removing this line from your code once and build then.
[centerView setTranslatesAutoresizingMaskIntoConstraints:NO];

how to apply a constraint programmatically to UILabel such that it is 20px above from bottom of the view in Objective C

I am implementing autolayout in my app.I want the UILabel to be placed 20px from bottom of the screen by using autolayout programmatically.Please help me out where i am going wrong.With the below code UILabel is getting not displayed on applying bottom constrint.Below is my code.
lblSwipe=[[UILabel alloc]init];
lblSwipe.text=#"Swipe to learn more";
lblSwipe.textColor=[UIColor lightGrayColor];
lblSwipe.textAlignment = NSTextAlignmentCenter;
lblSwipe.backgroundColor=[UIColor yellowColor];
lblSwipe.font=[UIFont systemFontOfSize:14.0];
[self.view addSubview:lblSwipe];
[lblSwipe setTranslatesAutoresizingMaskIntoConstraints:NO];
//to position UILabel in centre of the screen
NSLayoutConstraint *myConstraint=[NSLayoutConstraint
constraintWithItem:lblSwipe
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
[self.view addConstraint:myConstraint];
//to apply a fixed width of 300 to UILabel
[lblSwipe addConstraint:[NSLayoutConstraint constraintWithItem:lblSwipe
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:300.0]];
//to place UILabel 20px above from bottom of the screen
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:lblSwipe
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:20.0]];
The constant for the bottom constraint has to be -20 (because above means negative offset).

Resources