I am using iCarousel to implement a cover flow. I provide views to it in my iCarousel data source method, like this:
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
if(!view) {
view = [[SDHotOnThumbView alloc]initWithFrame:CGRectMake(0, 0, kHotOnThumbViewWidth, kHotOnThumbViewHeight)];
view.contentMode = UIViewContentModeScaleAspectFit;
}
[((SDHotOnThumbView*)view) setData:[self.dataArray objectAtIndex:index]];
return view;
}
The method setData apart from setting the label's text in SDHotOnThumbView instance, and starting image download for the image view in it, also makes a call to a method updateLabelLayoutWithAttributedText, which is implemented as under in the SDHotOnThumbView class:
-(void)updateLabelLayoutWithAttributedText:(NSMutableAttributedString*) labelString
{
float height = [self heightOfAttributedText:labelString width:self.frame.size.width];
self.descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
//self.descriptionLabel.preferredMaxLayoutWidth = self.frame.size.width;
NSDictionary *viewsDict = #{#"desclabel":self.descriptionLabel};
NSArray *constraintsH = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0- [desclabel]-0-|" options:0 metrics:nil views:viewsDict];
[self addConstraints:constraintsH];
NSArray *constraintsV = [NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[desclabel(==%f)]-10-|", height]
options:0
metrics:nil
views:viewsDict];
[self addConstraints:constraintsV];
}
iCarousel recycles views, and I have to allocate a view if the recycled view is nil, and configure it every time the carousel asks for a view at a given index. Based on the size of an attributed string, I update vertical constraints for the label with the new height, in the second method I have attached, so that the label is resized as per the data it contains. The views layout just fine, except that I get . For example, I get
Unable to simultaneously satisfy constraints.
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:0x7b82f330 V:[UILabel:0x7d861820'SanDisk Cruzer Blade USB ...'(51.2)]>",
"<NSLayoutConstraint:0x7d862df0 V:[UILabel:0x7d861820'SanDisk Cruzer Blade USB ...'(65.6)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7d862df0 V:[UILabel:0x7d861820'SanDisk Cruzer Blade USB ...'(65.6)]>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in
<UIKit/UIView.h> may also be helpful.
Logging computed heights for the label, I can see that probably when the cell is removed from the collection view and re-added for some different index in the collection view, it probably holds on to its old height, and tries to add a new vertical constraint, breaking the previous one. I even tried removing all label constraints from the view, and adding everytime set data is called, but i see the same happening in Xcode, even though everything seems to work as expected. How do I go about these Xcode warnings, or is it fine if the layout is as desired.
Somehow you doubly inserted 2 different heights to the label, the first one's height is 51.2 and the second one is 65.6. Please check your code once more.
Related
I have a scrollview and a separate UIView where I placed a series of textFields and labels with constraints which fully occupies the top and bottom. I'm trying to adjust the UIView's height based on its subview constraints but it won't. What is happening is that the view keeps its height and force other textfields to collapse or shrink thus breaking the constraints.
Details
Each subview priority values :
compression = 750
hugging = 250
UIView priority values:
compression = 249
hugging = 749 Set to be lower than the rest.
Most of the textfields has aspect ratio constraint. This causes the field to adjust.
Each subview has vertical/top/bottom spacing between each other. The top and bottom elements has top and bottom constraints to the view as well.
What's on my code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* I had to adjust the UIView's width to fill the entire self.view.*/
if(![contentView isDescendantOfView:detailsScrollView]){
CGRect r = contentView.frame;
r.size.width = self.view.frame.size.width;
contentView.frame = r;
[detailsScrollView addSubview:contentView];
}
}
Screenshots
The view
This is what currently happens. In this instance it forces the email field to shrink. If I place a height value on it, it does not shrink but the layout engine finds another element to break
Edit:
Solved
Maybe I just needed some break to freshen up a bit. I did tried using constraints before but got no luck. However thanks to the suggestion I went back setting the constraints instead of setting the frame on this one and got it finally working.
Solution:
-(void)viewDidLoad{
[detailsScrollView addSubview:contentView];
[contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
[detailsScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView,detailsScrollView);
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views:viewsDictionary];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:viewsDictionary];
NSArray *widthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView(==detailsScrollView)]-0-|" options:0 metrics:nil views:viewsDictionary];
}
When you use interface builder to deal with the UIScrollView and its child UIView. usually a top, bottom, left and equal width constraints are set between the UIScrollView and its child which is the contentView in your case.
Without those constraints the other option is to set the content size of the UIScrollView. which was the way of using the UIScrollView before introducing constraints.
So, 1. you should add those constraints programmatically.
By using the constraints, the views frame is no longer needed to resize the views.
So, 2. remove frame setting for your content view.
I am not so happy with the way you set the frame in the viewDidLayoutMethod. if I am going to do that here I would take the frame setting out of the if statement.
The code would be as follow with no if statement:
[detailsScrollView addSubview:contentView];
// then set the constraints here after adding the subview.
Put this code anywhere but not inside your viewDidLayoutSubviews method. it will be a bigger problem than setting the frame in there inside if statement.
Note: Originally, if you are going to set frame in the viewDidLayoutSubviews
method. you should do it for all cases. for example for the if case
and the else case. because, next time this method is going to be
called the views will respond to the constraint. and lose its frame.
Another observation: if you want the view to response to its subviews constraint why you need to set the frame for it? right?
After adding the constraint you may need to call the method constraintNeedsUpdate or another related method.
I'm attempting to set up autolayout for a paging scroll view with a dynamic number of pages (one primary subview per page). My view hierarchy is set up as follows:
Main view
Scroll view
UIView (fits content)
TutorialSubview
...
After adding all the views to an array, I have the following code to dynamically generate constraints:
self.iphoneSVContentWConstr.constant = (self.subVWidth * self.contentSV.frame.size.width);
NSMutableDictionary *views = [NSMutableDictionary new];
NSLayoutFormatOptions formatForVert = (NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom);
NSMutableString *format = [NSMutableString stringWithString:#"|"];
int idx = 0; for (UIView *v in self.viewArr) {
NSString *key = [NSString stringWithFormat:#"View%i", idx];
[views setObject:v forKey:key];
[format appendFormat:#"[%#(%.f)]", key, self.subVWidth];
idx++;
}
[format appendString:#"|"];
NSLog(#"%#", format);
//Update the content view
[self.iphoneSVContent addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:formatForVert metrics:nil views:views]];
[self.contentSV layoutIfNeeded];
This ends up outputting:
|[View0(320)][View1(320)][View2(320)][View3(320)]|
Which seems correct. However, I'm getting the following errors thrown:
Unable to simultaneously satisfy constraints.
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:0x17409f1d0 H:[UIView:0x174197c40(102400)]>",
"<NSLayoutConstraint:0x1742811d0 H:|-(0)-[TutorialSubview:0x1743a1960] (Names: '|':UIView:0x174197c40 )>",
"<NSLayoutConstraint:0x170287490 H:[TutorialSubview:0x1743a1960(320)]>",
"<NSLayoutConstraint:0x1702873f0 H:[TutorialSubview:0x1743a1960]-(0)-[TutorialSubview:0x1743a3100]>",
"<NSLayoutConstraint:0x1702871c0 H:[TutorialSubview:0x1743a3100(320)]>",
"<NSLayoutConstraint:0x1702872b0 H:[TutorialSubview:0x1743a3100]-(0)-[TutorialSubview:0x1743a3480]>",
"<NSLayoutConstraint:0x170287210 H:[TutorialSubview:0x1743a3480(320)]>",
"<NSLayoutConstraint:0x170286fe0 H:[TutorialSubview:0x1743a3480]-(0)-[TutorialSubview:0x1703a31e0]>",
"<NSLayoutConstraint:0x170287080 H:[TutorialSubview:0x1703a31e0(320)]>",
"<NSLayoutConstraint:0x170286f40 H:[TutorialSubview:0x1703a31e0]-(0)-| (Names: '|':UIView:0x174197c40 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x170286fe0 H:[TutorialSubview:0x1743a3480]-(0)-[TutorialSubview:0x1703a31e0]>
I have the following constraints set up on self.iphoneSVContent (the view they're all being added to within the scroll view):
At this point, I'm just unsure as to what's causing the issue. Any insight is much appreciated!
The problem seems to be the first constraint you are changing the constant
self.iphoneSVContentWConstr.constant = (self.subVWidth * self.contentSV.frame.size.width);
The value for the view's width you set with this constraint (which is 102400, from the conflicting constraints output) is not the same as 4* 320 (which is the width of the views inside it).
If the width of the UIView inside the scrollview is defined completely by the number of subviews it has inside (which already have defined width), you should not need this constraint self.iphoneSVContentWConstr, because you have enough constraints for the width to be calculated unambiguously.
If you still want to keep this width constraint, make sure you set the constant to the correct value, with something like:
self.iphoneSVContentWConstr.constant = (self.subVWidth * self.viewArr.count);
I suppose the width/height of the scrollview you set in the storyboard. That should be enough for it to work and for the constraints to not crash anymore.
I really liked the way you constructed the visual format string! Cool :)
Let me know how it went. Good luck!
I have the following setup:
2 view controllers defined in storyboard.
I have a custom segue that gets triggered by activating a control on the first vc.
in the -(void)perform method of the segue, I have to add the second vc's view as subview to the first with some animation.
-(void)perform{
MyVC *myVC = self.destinationViewController;
UIViewController *sourceVC = self.sourceViewController;
UIView *myView = myVC.view;
[sourceVC.view addSubview:myView];
NSDictionary *viewDictionary = NSDictionaryOfVariableBindings(sourceVC.view, drawerView);
[myView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[myView(200)]"
options:0 metrics:nil views:viewDictionary]];
}
Unfortunately I get the following error.
2014-10-31 16:57:33.899 ReviewsAgain[19971:5435238] Unable to simultaneously satisfy constraints.
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:0x7fb0306368c0 H:[UIView:0x7fb0306337e0(200)]>",
"<NSLayoutConstraint:0x7fb030635c80 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7fb0306337e0(375)]>"
)
What I believe is happening here - myView has its constraints applied at build time 'from its own vc`, and when I am to add it as a subview to another view - things get messy and it cannot understand which constrains to apply - the ones applied earlier - or the ones I have defined.
I wonder what is the right way to solve this ?
So in order for this to work the view which is being 'taken' from its own viewController must have its setTranslatesAutoresizingMaskIntoConstraints set to NO.
[myView setTranslatesAutoresizingMaskIntoConstraints:NO];
I have a UITableViewCell subclass which contains a multiline label, and I would like the cell to size itself dynamically based on the content of that label. I'm aware that iOS 8 introduced auto-sizing cells based on AutoLayout constraints, and I've found several examples of this already on SO, but I'm still having some trouble implementing this behavior properly.
Here's my updateConstraints implementation:
- (void)updateConstraints {
[super updateConstraints];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[_nameLabel(==20)]-10-[_tweetLabel]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_nameLabel, _tweetLabel)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[_avatarView]-10-[_nameLabel]-10-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_avatarView, _nameLabel)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[_nameLabel]-10-[_tweetLabel]-10-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_nameLabel, _tweetLabel)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[_avatarView]-10-[_tweetLabel]-10-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_avatarView, _tweetLabel)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[_avatarView(==45)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_avatarView)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[_avatarView(==45)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_avatarView)]];
}
In the table view controller I set the row height to UITableViewAutomaticDimension (and I set an estimated row height as well). At runtime, I get a series of auto layout errors and all of the table view cells appear nearly completely overlapped.
The auto layout conflicts are between the following constraints:
V:|-(10)-[_nameLabel]
V:[_nameLabel(20)]
V:[_nameLabel]-(10)-[_tweetLabel]
V:[_tweetLabel]-(10)-|
V:[cell(44)]
I suspect the last constraint, "UIView-Encapsulated-Layout-Height", which forces a height of 44, is the cause of the issue, but I'm not quite sure where that comes from, so hopefully somebody can shed some light on the issue.
In order to implement automatic row heights for table view cells, you need to do the following:
Implement Auto Layout constraints within the cell's contentView that allow the view to express its preferred height. Be sure to set UILabels to word wrap over multiple lines.
Be sure you've defined an axial chain of constraints in both dimensions, that is, constraints that collectively bind all the way from one edge of the view to the other. Perhaps the easiest way to be sure these constraints are correct is to implement your custom content as a plain old UIView (which is easy to test), and then use constraints so that the UITableViewCell.contentView hugs that view. (I use this gist to automate building the "view-wrapping cell".)
Set tableView.rowHeight = UITableViewAutomaticDimension
Set tableView.estimatedRowHeight = 400 or some other reasonably generous value, in order to workaround some UIKit bugs when the estimate is too low.
I have spent a puzzling amount of time working with this feature. This github repo shows seven complete examples of self-sizing table view cells containing a single label of wrapping text -- programmatic, nib-based, storyboard-based, etc..
Finally, do not worry too much if you see warnings about unsatisfiable constraints mentioning "UIView-Encapsulated-Layout-Height" or similar at the first time the table view loads. This is an artefact of UITableView's initial process for creating a cell, determining what its size should be based on Auto Layout Constraints, and keeping the UITableViewCell tightly wrapping its contentView. The repo I mentioned above has more extensive discussion and code for exploring this somewhat awkward corner of the API.
You should only worry about constraint-violation warnings if they persist even after the cell has loaded and has scrolled a bit, or if you are seeing incorrect layouts initially. In this case, again, the first step should always be to ensure your constraints are correct by developing them and testing them in isolation if possible, in a plain UIView.
I just came across this issue.
From numerous other Stackoverflow post they recommend:
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
That didn't work for me at first. I found that I also need to do:
self.frame = CGRectMake(0, 0, self.frame.size.width, 50);
My custom cell's init method looks like his:
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if(self)
{
[self initViews];
[self initConstraints];
}
return self;
}
I put the code in my "initViews" method:
-(void)initViews
{
...
// fixes an iOS 8 issue with UIViewEncapsulated height 44 bug
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.frame = CGRectMake(0, 0, self.frame.size.width, 50);
}
The problem went away and my cell looks correct too.
Does this work for you?
Are you sure you have -translatesAutoresizingMaskIntoConstraints set to NO on the cell? If you don't, the system generates constraints based on the autoresizing mask, which was the previous way of doing layout on iOS, and should be disabled when using Auto Layout.
Ok, so I've created a UIView in interface builder. I'm using AutoLayout and I've got one subview of this view pinned to all four sides.
Here's what I don't understand. When I load this NIB file using loadNibNamed. I then get a reference to the view. I set the frame for this view. And yet, when I access the subview (using [containerView viewWithTag:1]) it's frame hasn't been automatically resized. What gives? If you change the frame for a parent view, why wouldn't the subview frame change as well?
It doesn't make any sense.
Why can't you just load a UIView, set it's frame and have all the subviews adjust as appropriate (ESPECIALLY since I'm using AutoLayout!)?
EDIT: To be clear, all I want to do is be able to define a UIView hierarchy in IB with appropriate AutoLayout constraints and then be able to load and display that view on the screen sometimes at different sizes? Why is this so hard?
UIKit doesn't update subview geometry immediately when you change a view's geometry. It batches up the updates for efficiency.
After running your event handler, UIKit checks whether any views in the on-screen window hierarchy need to be laid out. If it finds any, it lays them out by solving your layout constraints (if you have any) and then sending layoutSubviews.
If you want to solve the constraints and lay out a view's subviews immediately, simply send layoutIfNeeded to the view:
someView.frame = CGRectMake(0, 0, 200, 300);
[someView layoutIfNeeded];
// The frames of someView.subviews are now up-to-date.
I too had the same problem, I was creating a tutorial view where in which I wanted to add multiple UIViews to a scrollview. While I was trying to get the frame from xib, it gave always 320 and because of that the offset for the pages were wrong and my views looked crappy in iPhone6 and 6plus.
I then used pure autolayout approach, ie instead of using the frame, I added constraints through VFL so that subviews fit exactly to the superview. Below is the snapshot of code where I create around 20 UIViews from Xib and add properly to scrollview
Full code here ScrollViewAutolayout
Method to layout the childviews in the scrollview.
#param nil
#result layout the child views
*/
-(void)layoutViews
{
NSMutableString *horizontalString = [NSMutableString string];
// Keep the start of the horizontal constraint
[horizontalString appendString:#"H:|"];
for (int i=0; i<viewsArray.count; i++) {
// Here I am providing the index of the array as the view name key in the dictionary
[viewsDict setObject:viewsArray[i] forKey:[NSString stringWithFormat:#"v%d",i]];
// Since we are having only one view vertically, then we need to add the constraint now itself. Since we need to have fullscreen, we are giving height equal to the superview.
NSString *verticalString = [NSString stringWithFormat:#"V:|[%#(==parent)]|", [NSString stringWithFormat:#"v%d",i]];
// add the constraint
[contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalString options:0 metrics:nil views:viewsDict]];
// Since we need to horizontally arrange, we construct a string, with all the views in array looped and here also we have fullwidth of superview.
[horizontalString appendString:[NSString stringWithFormat:#"[%#(==parent)]", [NSString stringWithFormat:#"v%d",i]]];
}
// Close the string with the parent
[horizontalString appendString:#"|"];
// apply the constraint
[contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:horizontalString options:0 metrics:nil views:viewsDict]];
}
Unfortunately the accepted answer by Rob didn't work for me. This is what worked:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"myXib" owner:self options:nil];
[self addSubview:views[0]];
self.subviews[0].frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); //ADDED THIS FOR PROPER SIZE
}
return self;
}