Custom TableViewCell with dynamic height and full width image using Autolayout - ios

I'm really stuck using autolayout and a custom tableviewcell.
I want my tableviewcell to have a dynamic height. The table cell should contain an image with the same width as the tableviewcell and the height should adjust based on an aspect ratio of 0.6
This is what it should look like
But this is what I get
To give you a clear look of what is going on I gave the tableViewCell and it's subviews some pretty background colors…
Green = thumbnailView
Blue = self
Red = contentView
Purple = hotspotNameLabel
Yellow = categoryLabel
This is what my code looks like:
Initializer:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.contentView.backgroundColor = [UIColor redColor];
self.backgroundColor = [UIColor blueColor];
CGFloat imageAspectRatio = 720/432;
CGFloat imageHeight = self.contentView.bounds.size.width / imageAspectRatio;
CGRect imageFrame = CGRectMake(0, 0, self.contentView.bounds.size.width,imageHeight);
self.thumbnailView = [[UIImageView alloc] initWithFrame:imageFrame];
[self.thumbnailView setBackgroundColor:[UIColor greenColor]];
[self.thumbnailView setContentMode:UIViewContentModeScaleAspectFill];
[self.thumbnailView.layer setMasksToBounds:true];
[self.contentView addSubview:self.thumbnailView];
self.hotspotNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 15, self.contentView.bounds.size.width - 80, 20)];
[self.hotspotNameLabel setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[self.hotspotNameLabel setTextColor:[UIColor colorWithWhite:(32/255.0) alpha:1]];
[self.hotspotNameLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.hotspotNameLabel setNumberOfLines:0]; // Unlimited Lines
[self.hotspotNameLabel setBackgroundColor:[UIColor purpleColor]];
[self.hotspotNameLabel setFont:[UIFont fontWithName:#"AvenirNext-Medium" size:20]];
[self.contentView addSubview:self.hotspotNameLabel];
// Category Label
self.categoryLabel = [[CategoryLabel alloc] initWithFrame:CGRectMake(40, 15, self.contentView.bounds.size.width - 80, 10)
categoryId:22 categoryName:#"Food"];
[self.contentView addSubview:self.categoryLabel];
// Constraints
[self.contentView setTranslatesAutoresizingMaskIntoConstraints:false];
[self.hotspotNameLabel setTranslatesAutoresizingMaskIntoConstraints:false]; // Enables autosizing
[self.categoryLabel setTranslatesAutoresizingMaskIntoConstraints:false];
[self.thumbnailView setTranslatesAutoresizingMaskIntoConstraints:false];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.thumbnailView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem: self.contentView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.thumbnailView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem: self.contentView attribute:NSLayoutAttributeWidth multiplier:0.6f constant:0]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[nameLabel]-0-|" options:0 metrics:nil views:#{#"nameLabel": self.hotspotNameLabel}]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[thumbnail]-20-[nameLabel]-10-[categoryLabel]-0-|" options:0 metrics:nil views:#{#"thumbnail": self.thumbnailView,#"nameLabel": self.hotspotNameLabel, #"categoryLabel":self.categoryLabel}]];
}
return self;}
Layout subviews:
-(void)layoutSubviews {
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
self.hotspotNameLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.hotspotNameLabel.bounds);
}
tableView:HeightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForLargeCellAtIndexPath:indexPath];
}
- (CGFloat)heightForLargeCellAtIndexPath:(NSIndexPath *)indexPath {
LargeHotspotCell *sizingCell = [[LargeHotspotCell alloc] init];
[self configureLargeCell:sizingCell atIndexPath:indexPath];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGFloat height = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height + 1 ;
}

Here you are my little tutorial with explaining basic concepts of autolayout.
Your first step, should be subclassing UITableviewCell, I named my new cell DynamicTableViewCell and assigned it in the identity inspector.
I dragged and dropped UILabel and UIImageView into contentView.
Below a screenshot with UILabel constraints, I added simultaneously leading and trailing space constraints, in order to make my label equal to contentView width. And it is aligned to the bottom of the cell.
Now, it is time to assign constraints to UIImageView. I am adding aspect ratio 0.6 (imageHeight/imageWidth = 0.6) and adding leading and trailing space to contentView. Feel free to play with constants to achieve your desired purposes.
Final results in iphone5, iphone6 simulator
Your image height (green color) is 60pt in 5s screen, 70pt in 6 screen and width resized properly:
For your better understanding, I am publishing Working Sample Project on github

Related

Get frame of a view after autolayout

I have a method:
- (void)underlineTextField:(UITextField *)tf {
CGFloat x = tf.frame.origin.x-8;
CGFloat y = tf.origin.y+tf.frame.size.height+1;
CGFloat width = self.inputView.frame.size.width-16;
UIView *line = [[UIView alloc] initWithFrame:(CGRect){x,y,width,1}];
line.backgroundColor = [UIColor whiteColor];
[self.inputView addSubview:line];
}
That underlines an input UITextField; The textfield has a width that changes depending on the screen width (nib autolayout).
I have tried using
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
and
[self.inputView setNeedsLayout];
[self.inputView layoutIfNeeded];
before I call this method with no change in result. the resulting line is much wider than the UITextField (it matches the original size in the Nib).
I just want the resulting frame of the UITextField in question after being processed by the autolayout
SOLUTION: (using 'Masonry' Autolayout)
- (UIView *)underlineTextField:(UITextField *)tf {
UIView *line = [[UIView alloc] initWithFrame:CGRectZero];
line.backgroundColor = [UIColor whiteColor];
[self.inputView addSubview:line];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(tf.mas_centerX);
make.width.equalTo(tf.mas_width).with.offset(16);
make.height.equalTo(#1);
make.top.equalTo(tf.mas_bottom);
}];
return line;
}
Your underline view has a static frame, it is not connected to the textField through constraints. Instead of setting the frame, add constraints to self.inputView
- (void)underlineTextField:(UITextField *)tf {
UIView *line = [[UIView alloc] init];
line.backgroundColor = [UIColor whiteColor];
[line setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.inputView addSubview:line];
// Vertical constraints
[self.inputView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[line(==1)]-(-1)-|" options:0 metrics:nil views:#{ #"line": line}]];
// Horizontal constraints
[self.inputView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(-8)-[line]-8-|" options:0 metrics:nil views:#{ #"line": line}]];
[self.inputView layoutIfNeeded];
}
After the layoutIfNeeded call, the frame for your view should be right. I hoped I got the constants right. Because the line appears one unit under the textView make sure to unset Clip Subviews in the storyboard for the textField
I hope this works for you. Let me know if you have questions!
You could adjust the frame of the underline in viewDidLayoutSubviews which is called after auto layout has set all the frames.
Note that you're creating a new view and calling addSubview: each time. You should do that once when you create the text view, outside of this method. Otherwise you'll create a new UIView every time your user rotates the device.

how to set subview of UIScrollView using autolayout

i am trying to implement Horizontal scrollview with ten views.
its working fine in portrait mode but i want same scenario in landscape too.
can anyone help me by which constraint i can achieve this?
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title=#"Demo.....";
self.myScrollView.pagingEnabled = YES;
NSInteger numberOfViews = 10;
for (int i = 0; i < numberOfViews; i++) {
CGFloat myOrigin = i * self.view.frame.size.width;
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(myOrigin, 0, self.view.frame.size.width, self.view.frame.size.height)];
myView.backgroundColor = [UIColor purpleColor];
CGRect myFrame = CGRectMake(10.0f, 70.0f, 200.0f, 25.0f);
UILabel *myLabel = [[UILabel alloc] initWithFrame:myFrame];
myLabel.translatesAutoresizingMaskIntoConstraints = NO;
myLabel.text = [NSString stringWithFormat:#"This is page number %d", i];
myLabel.font = [UIFont boldSystemFontOfSize:16.0f];
myLabel.textAlignment = NSTextAlignmentLeft;
[myView addSubview:myLabel];
//set the scroll view delegate to self so that we can listen for changes
self.myScrollView.delegate = self;
//add the subview to the scroll view
[self.myScrollView addSubview:myView];
NSLayoutConstraint *constraintLeft = [NSLayoutConstraint constraintWithItem:myView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.myScrollView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0.0];
// 0px to the right of the UIScrollView
NSLayoutConstraint *constraintRight = [NSLayoutConstraint constraintWithItem:myView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.myScrollView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0.0];
[self.myScrollView addConstraint:constraintLeft];
[self.myScrollView addConstraint:constraintRight];
}
//set the content size of the scroll view, we keep the height same so it will only
//scroll horizontally
self.myScrollView.contentSize = CGSizeMake(self.view.frame.size.width * numberOfViews,
self.view.frame.size.height);
//we set the origin to the 3rd page
CGPoint scrollPoint = CGPointMake(self.view.frame.size.width * 2, 0);
//change the scroll view offset the the 3rd page so it will start from there
[myScrollView setContentOffset:scrollPoint animated:YES];
[self.view addSubview:self.myScrollView];
}
in my above code,
ten view is completely added into scrollview and working fine in portrait mode but when i change the orientation to horizontal it will not resize and not scrolled.
You need to tie subviews width constraint equal to scrollView width
for (UIView *subView in subViews)
{
NSLayoutConstraint *constraintEqualWidth = [NSLayoutConstraint constraintWithItem:subView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.myScrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0];
[subView addConstraint:constraintEqualWidth];
}

Cannot satisfy constraints - Visual Format Language

I started to use autolayout and constraints programatically, but I cannot understand why I get the following warning
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x7fbbd142ead0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7fbbd162ec70(44)]>",
"<NSLayoutConstraint:0x7fbbd1431740 V:|-(20)-[UILabel:0x7fbbd174e520'Liked'] (Names: '|':UITableViewCellContentView:0x7fbbd162ec70 )>",
"<NSLayoutConstraint:0x7fbbd1431790 V:[UILabel:0x7fbbd174e520'Liked']-(NSSpace(8))-[UILabel:0x7fbbd174e7e0's']>",
"<NSLayoutConstraint:0x7fbbd14317e0 V:[UILabel:0x7fbbd174e7e0's']-(20)-| (Names: '|':UITableViewCellContentView:0x7fbbd162ec70 )>"
)
I know that the line of code that is causing the error is:
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
but I cannot understand why the constraint cannot be satisfied. This can be a silly question, but I am new to autolayout and the topic is confusing, because of the incomplete documentation. I tried different variations and solutions but failed to find the correct one. What am I missing? I am also pasting the code of my custom table view cell for more detailed inspection.
#implementation NotificationCell
#synthesize nameLbl,notificationLbl,textLabel,timeLbl,profileImgView,notificationImgView,clockImgView,didSetupConstraints;
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
nameLbl = [[UILabel alloc]init];
[nameLbl setTextColor:[UIColor blueColor]];
nameLbl.translatesAutoresizingMaskIntoConstraints = NO;
[nameLbl setTextAlignment:NSTextAlignmentLeft];
[nameLbl setNumberOfLines:1];
[nameLbl setBackgroundColor:[UIColor redColor]];
[self.contentView addSubview:nameLbl];
notificationLbl = [[UILabel alloc]init];
[notificationLbl setTextColor:[UIColor lightGrayColor]];
notificationLbl.translatesAutoresizingMaskIntoConstraints = NO;
[notificationLbl setTextAlignment:NSTextAlignmentLeft];
[notificationLbl setNumberOfLines:1];
[self.contentView addSubview:notificationLbl];
textLabel = [[UILabel alloc]init];
[textLabel setTextColor:[UIColor blueColor]];
textLabel.translatesAutoresizingMaskIntoConstraints = NO;
[textLabel setTextAlignment:NSTextAlignmentLeft];
[textLabel setNumberOfLines:0];
[textLabel setBackgroundColor:[UIColor grayColor]];
[self.contentView addSubview:textLabel];
timeLbl = [[UILabel alloc]init];
[timeLbl setTextColor:[UIColor grayColor]];
timeLbl.translatesAutoresizingMaskIntoConstraints = NO;
[timeLbl setTextAlignment:NSTextAlignmentLeft];
[timeLbl setNumberOfLines:1];
[self.contentView addSubview:timeLbl];
profileImgView = [[UIImageView alloc]init];
[profileImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
[profileImgView setBackgroundColor:[UIColor redColor]];
[profileImgView.layer setCornerRadius:20];
[profileImgView.layer setMasksToBounds:YES];
[self.contentView addSubview:profileImgView];
clockImgView = [[UIImageView alloc]init];
[clockImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
[clockImgView setBackgroundColor:[UIColor blueColor]];
[clockImgView.layer setCornerRadius:10];
[clockImgView.layer setMasksToBounds:YES];
[self.contentView addSubview:clockImgView];
notificationImgView = [[UIImageView alloc]init];
[notificationImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
[notificationImgView setBackgroundColor:[UIColor purpleColor]];
[notificationImgView.layer setCornerRadius:10];
[notificationImgView.layer setMasksToBounds:YES];
[self.contentView addSubview:notificationImgView];
/*[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[_headerView(==80)]-0-[_tableView]-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:elementsDict]];*/
}
return self;
}
-(void)updateConstraints{
if (self.didSetupConstraints == NO) {
NSDictionary *views = NSDictionaryOfVariableBindings(nameLbl,notificationLbl,notificationImgView,textLabel,timeLbl,profileImgView,clockImgView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[profileImgView(40)]-10-[textLabel]-[clockImgView(20)]-[timeLbl(20)]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[profileImgView]-10-[nameLbl]-[clockImgView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[profileImgView]-10-[notificationImgView(20)]-[notificationLbl]-[clockImgView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[notificationImgView(20)]-[textLabel]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[profileImgView(40)]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[clockImgView(20)]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[timeLbl]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
self.textLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.textLabel.frame);
}
A couple of thoughts:
Make sure you are neither explicitly setting rowHeight property on the table view, nor implementing tableView:heightForRowAtIndexPath: in your UITableViewDelegate. Either one of those will establish conflicting constraints with a cell that performs constraint-based height calculations.
While I haven't found it necessary, "Luke said" (in WWDC 2014 video What's New in Table and Collection Views) one should set estimatedRowHeight for the table view (e.g. in viewDidLoad) when using constraint generated row heights.
self.tableView.estimatedRowHeight = 44;
I also haven't found it necessary to do so, but in the same WWDC 2014 video, the presenter, Luke, was getting strange constraint errors similar to yours until he added the following line to his code:
self.tableView.rowHeight = UITableViewAutomaticDimension;
He said that this wouldn't be necessary in the next seed, but in his demonstration, it was necessary.
I've noticed that you can still often receive the unsatisfiable constraints error (even if you've done the above and all of your constraints are fine). I have found that if you change one of your cell's vertical constraints to have a priority less than 1000, it seems to resolve this issue (and the cell appears to be formatted properly.
Note, this behavior of automatically calculated row heights differs in iOS 7. But in iOS 8, the above should do it. If you're still having problems, create a test project that manifests the problem and upload it somewhere where we can look at it.
This happens because the height of your cell is 44 points, however you have vertically placed labels, where only the spaces are 20 + std spacing + std spacing + 20 which is definitely more than 44, and there still is the label height, which you have to take into account: V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|. So you should increase the height of your cell.
you need to set self.tableView.rowHeight = UITableViewAutomaticDimension; It will make tableViewCell height dynamically.
you might also need to set height constraints to label greater then equally so that if not content it's height can be 0.
Some suggestions from my experience with dynamic cell heights:
First, do not override the updateConstraints method in the NotificationCell. instead add your constraints in the init method, after you added all the subviews to the contentView.
Then, change your implementation of heightForRow
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath {
NotificationCell *cell = [[NotificationCell alloc] init];
// Do all your configuration here
cell.nameLabel.text = [_names objectAtIndex:indexPath.row];
// Add all the rest too ...
CGFloat cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingSizeCompressedSize].height;
// Add a breakpoint here to see if your constraints are right and if the height is calculated correctly
return height + 1;
}
If your labels are not multiline, even without setting the text, the height should be calculated correctly. However, if they can stretch over multiple lines, then you need to set the value for the text in the heightForRow method.
Hope this fixes your problem.

iOS 8 UITableViewCell extra broken separators

OK, I don't know what's up with iOS 8 but when I run my app on it, I see these extra UITableViewSeparatorViews:
Update
I used the Xcode 6 View Debugger and I see that in this problematic UITableView, I see these extra views being added as a subview of my UITableViewCellContentView
_UITableViewCellSeparatorView
Why does iOS 8 add these random subviews to my cell's contentView ?
As you can see from the screenshot, those extra lines are the ones that doesn't reach the edge of the screen.
In my view controller, I am using the following code to make the lines go all the way to the edge of the screen:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if ([tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([tableView respondsToSelector:#selector(setLayoutMargins:)]) {
[tableView setLayoutMargins:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
In my custom cell class, I am setting:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_reuseID = reuseIdentifier;
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.contentView.backgroundColor = [[BSGThemeManager sharedTheme] clearColor];
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.containerView = [[UIView alloc] init];
_photo = [[BSGUserPhotoView alloc] init];
[_photo clipPhotoToRadius:37.5];
//_photo.layer.cornerRadius = 37.5 / 2.0;
//_photo.layer.borderColor = [[BSGThemeManager sharedTheme] photoBorderColor];
//_photo.layer.borderWidth = [[BSGThemeManager sharedTheme] photoBorderWidth];
//_photo.clipsToBounds = YES;
self.photo.alpha = 0;
_message = [[UILabel alloc] init];
_message.backgroundColor = [[BSGThemeManager sharedTheme] clearColor];
_message.numberOfLines = 0;
_message.lineBreakMode = NSLineBreakByWordWrapping;
_message.font = [[BSGThemeManager sharedTheme] varelaRoundFontWithSize:15];
self.message.alpha = 0;
_time = [[UILabel alloc] init];
_time.textAlignment = NSTextAlignmentRight;
_time.font = [[BSGThemeManager sharedTheme] helveticaNeueFontWithSize:16];
_time.textColor = [[BSGThemeManager sharedTheme] postTimeColor];
[_time setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[_time setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
self.time.alpha = 0;
[self.containerView addSubview:_photo];
[self.containerView addSubview:_message];
[self.containerView addSubview:_time];
[self.contentView addSubview:self.containerView];
[self addAllConstraints];
}
return self;
}
I'm not sure if this line was the cause of it:
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
My Autolayout code for my cell is:
-(void)addAllConstraints
{
self.containerView.translatesAutoresizingMaskIntoConstraints = NO;
self.photo.translatesAutoresizingMaskIntoConstraints = NO;
self.message.translatesAutoresizingMaskIntoConstraints = NO;
self.time.translatesAutoresizingMaskIntoConstraints = NO;
id views = #{
#"containerView": self.containerView,
#"photo": self.photo,
#"message": self.message,
#"time": self.time
};
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView]|" options:0 metrics:nil views:views]];
// --------------------------------------------------
// Horizontal Constraints
// --------------------------------------------------
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[photo(37.5)]-15-[message(195)]" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[time]-10-|" options:0 metrics:nil views:views]];
// --------------------------------------------------
// Vertical Constraints
// --------------------------------------------------
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[photo(37.5)]" options:0 metrics:nil views:views]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.photo attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.message attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.photo attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.time attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.photo attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
}
Anyone else came across this weird iOS 8 bug?
It doesn't show up on iOS 7.
I found an easy solution, when i overwrote the layoutSubviews method, i also called the super method as well, like so:
-(void)layoutSubviews {
[super layoutSubviews];
// rest of custom implementation code here
. . .
}
Hope it helps
Change the highlighted to '0' then the it resolves. It was through XIB.
Lol....OK...I think I fixed this problem.
One of my subview is called "message" which is just a UILabel.
On my custom tableview cell's class, this code was causing the extra line to appear:
-(void)layoutSubviews
{
self.message.preferredMaxLayoutWidth = self.bounds.size.width * 0.75;
}
After getting rid of it, the cells behaved how I liked it to like in iOS 7.
For me, the dynamic height of the custom tableViewCells was causing an issue, to be specific, the heightForRowAtIndexPath and estimatedHeightForRowAtIndexPath delegate methods. One worked perfect for iOS 7 and the other for iOS 8. Solved it by conditionally checking for the iOS version.

Single Auto Layout constraint change breaks entire layout

I have a UIViewController which creates this layout:
The part that gives me trouble is the darker area on the bottom, containing the text.
The darker part is a UIScrollView subclass, which creates this layout using Auto Layout:
I have a couple of UILabels ("Omschrijving", "Informatie", and the small labels at the bottom of the view) and a UITextView (the view containing the text starting with "Robert Langdon").
I set the UITextViews height explicitly to 60 points, and when the "Meer" button is tapped, I calculate its full height using boundingRectWithSize:options:attributes:context. I then change its height constraint constant from its prior hardcoded value to the value I've calculated.
That's where it goes wrong. The layout is absolutely fine until the UITextView's height changes. All the content in the UIScrollView subclass seems to move inexplicably. I looked at the view hierarchy with Reveal: .
I've been messing with the constraints for hours now and I can't find a solution.
All the views have translatesAutoresizingMaskIntoConstraints set to NO.
This is iOS 8 on the simulator, with Xcode 6 b4.
My init methods looks like this:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.translatesAutoresizingMaskIntoConstraints = NO;
[self layoutIfNeeded];
[self addExpandButton];
[self setupUI];
[self setupConstraints];
[self layoutIfNeeded];
self.backgroundColor = [UIColor clearColor];
self.textView.backgroundColor = [UIColor clearColor];
self.textView.userInteractionEnabled = NO;
}
return self;
}
setupUI creates all the views and adds them to the scrollView. I've removed some repetitive lines of code for the sake of brevity
- (void)setupUI
{
// Initialization
UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectZero];
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 60.0) textContainer:nil];
textView.translatesAutoresizingMaskIntoConstraints = NO;
textView.editable = NO;
textView.textContainerInset = UIEdgeInsetsMake(0.0, -5.0, 0.0, -5.0);
textView.textColor = [UIColor whiteColor];
// init all the UILabels with CGRectZero frames
// ...
// ...
UIView *separator = [[UIView alloc] initWithFrame:CGRectZero];
separator.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.5];
// Set the text
descriptionLabel.text = #"Omschrijving";
textView.text = #"Robert Langdon, hoogleraar kunstgeschiedenis en symboliek, wordt op een nacht wakker in een ziekenhuis in Florence zonder te weten hoe hij daar is beland. Geholpen door een stoïcijnse jonge vrouw, Sienna Brooks, vlucht Langdon en raakt hij verzeild in een duizelingwekkend avontuur. Langdon ontdekt dat hij in het bezit is van een reeks verontrustende codes, gecreëerd door een briljante wetenschapper; een genie dat geobsedeerd is door het einde van de wereld en het duistere meesterwerk Inferno van Dante Alighieri.";
// Set the text of the labels
// ...
// ...
descriptionLabel.font = [UIFont fontWithName:#"ProximaNova-Semibold" size:14.0];
textView.font = [UIFont fontWithName:#"ProximaNova-Regular" size:12.0];
informationLabel.font = [UIFont fontWithName:#"ProximaNova-Semibold" size:14.0];
UIFont *font = [UIFont fontWithName:#"ProximaNova-Regular" size:10.0];
// Set the font of the labels
// ...
// ...
// Add the views to the view hierarchy
[self addSubview:descriptionLabel];
[self addSubview:textView];
[self addSubview:separator];
[self addSubview:informationLabel];
// Add the other labels as subviews
// ...
// ...
[self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)obj;
[label sizeToFit];
label.textColor = [UIColor whiteColor];
}
}];
// Assign the local properties to properties
// ...
// ...
for (UIView *view in self.subviews) {
view.translatesAutoresizingMaskIntoConstraints = NO;
}
}
Now on to the constraints. I have a big method that adds all the constraints called -addConstraints.
- (void)setupConstraints
{
// Omschrijving label top
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-3-[descriptionLabel]-3-[textView]"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:#{#"descriptionLabel": self.descriptionLabel,
#"textView": self.textView}]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.descriptionLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:3.0]];
// Omschrijving label leading
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-10-[descriptionLabel]"
options:0
metrics:nil
views:#{#"descriptionLabel": self.descriptionLabel}]];
// Text view width
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[textView(==width)]"
options:0
metrics:#{#"width": #220.0}
views:#{#"textView": self.textView}]];
// Text view height
NSArray *textViewHeightConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[textView(==60.0)]"
options:0
metrics:nil
views:#{#"textView": self.textView}];
[self addConstraints:textViewHeightConstraints];
self.textViewHeightConstraints = textViewHeightConstraints;
NSLog(#"%#", self.textViewHeightConstraints);
// Text view expand button
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[textView][expandButton(==12.0)]"
options:NSLayoutFormatAlignAllTrailing
metrics:nil
views:#{#"textView": self.textView, #"expandButton": self.expandButton}]];
// Separator
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[expandButton]-6-[separator]"
options:NSLayoutFormatAlignAllTrailing
metrics:nil
views:#{#"expandButton": self.expandButton, #"separator": self.separator}]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[separator(==textView)]"
options:0
metrics:nil
views:#{#"separator": self.separator, #"textView": self.textView}]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[separator(==0.5)]"
options:0
metrics:nil
views:#{#"separator": self.separator}]];
NSString *leftVisualFormatString = #"V:[separator]-6-[informationLabel]-spacing-[languageDescriptionLabel]-spacing-[categoryDescriptionLabel]-spacing-[publisherDescriptionLabel]-spacing-[publishedDateDescriptionLabel]-spacing-[pageCountDescriptionLabel]-|";
NSDictionary *descriptionViews = #{#"separator": self.separator,
#"informationLabel": self.informationLabel,
#"languageDescriptionLabel": self.languageDescriptionLabel,
#"categoryDescriptionLabel": self.categoryDescriptionLabel,
#"publisherDescriptionLabel": self.publisherDescriptionLabel,
#"publishedDateDescriptionLabel": self.publishedDateDescriptionLabel,
#"pageCountDescriptionLabel": self.pageCountDescriptionLabel};
NSDictionary *metrics = #{#"spacing": #1.0};
// All at once: vertical spacing and leading alignment for the labels on the left
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:leftVisualFormatString
options:NSLayoutFormatAlignAllLeading
metrics:metrics
views:descriptionViews]];
// Same, for the righthand labels
NSDictionary *views = #{#"languageLabel": self.languageLabel,
#"categoryLabel": self.categoryLabel,
#"publisherLabel": self.publisherLabel,
#"publishedDateLabel": self.publishedDateLabel,
#"pageCountLabel": self.pageCountLabel};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[pageCountDescriptionLabel]-20-[pageCountLabel]"
options:0
metrics:nil
views:#{#"pageCountDescriptionLabel": self.pageCountDescriptionLabel,
#"pageCountLabel": self.pageCountLabel}]];
NSString *rightVisualFormatString = #"V:[languageLabel]-spacing-[categoryLabel]-spacing-[publisherLabel]-spacing-[publishedDateLabel]-spacing-[pageCountLabel]-|";
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:rightVisualFormatString
options:NSLayoutFormatAlignAllLeading
metrics:metrics
views:views]];
}
As I said, this works fine until I call this method:
- (void)tappedExpandButton:(id)sender
{
if (self.textViewHeightConstraints.count == 1) {
NSLayoutConstraint *constraint = self.textViewHeightConstraints.firstObject;
CGFloat newHeight = [self.textView.text boundingRectWithSize:CGSizeMake(self.textView.textContainer.size.width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName: self.textView.font}
context:nil].size.height;
constraint.constant = ceilf(newHeight);
[self layoutIfNeeded];
}
}
Thanks already!
I think I've figured it out. For some reason I don't yet understand, the containing UIScrollView's frame changed when adding the constraint. That still seems really weird to me, since I think only its contentSize should change, but adding constraints from the dark area (which is a UIView) to the UIScrollView fixed it. This is the code:
[self.darkEffectView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[descriptionView]|"
options:0
metrics:nil
views:#{#"descriptionView": self.descriptionView}]];
[self.darkEffectView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[descriptionView]|"
options:0
metrics:nil
views:#{#"descriptionView": self.descriptionView}]];

Resources