UISegmentedControl (with images) in UITableViewCell (AutoLayout) always 29 points high - ios

Standard Tableview app. Autolayout. Each (cell) class has its own .xib file, no storyboard.
I have a segmented control in a tableview cell filling it (almost) completely, with all 4 edges bound with 6 points space to its container (=content view of the tableViewCell). The segments are made from images, not text.
I would expect when I call -[setImage: forSegmentAtIndex:] that the control changes its intrinsic content size to make enough space for the segment images - especially the view height.
I did set it's Content Hugging Priority to 251 (and the cell's hugging priority stays at default 250), so when I use narrow images the segmented control height should shrink (and the cell content view height also). And when I use larger images the segmented control height should grow...
I compute the correct height by measuring an invisible reference cell (dequeued myself in -[viewDidLoad] and kept in a viewController property),
asking Autolayout for -[systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] for the reference cell's contentView. This works fine, I get different sizes to return in -[tableView heightForRowAtIndexPath] for different images. So AutoLayout computes the height to be used correctly.
But the real (visible) cell of the tableView doesn't change the height of the segmented control. It always stays at 29 points (which is the standard height for a segmented control with text labels and not images), no matter what cellHeight being used and what images set.
I tried to add another NSLayoutConstraint to the segmented control, where I just set it's height:
heightConstraint = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:newHeight];
and add that (once) to the segmented control. I got "Unable to simultaneously satisfy constraints" when I tried NSLayoutRelationEqual, that's why I used NSLayoutRelationGreaterThanOrEqual. (Probably computed the newHeight value wrong).
But it still doesn't work - 29 points and garbled images in the segments...
What am I doing wrong? Or is this an iOS bug with UISegmentedControl and AutoLayout?
Of course I could just revert to manual layout and just set the frame of the segmented control - but I'd like to use AutoLayout...

I found a solution:
implement
-(CGSize)intrinsicContentSize
{
CGSize size = CGSizeMake(288, 68);
return size;
}
on my segmentedControl subclass. Well, of course compute the height based on the actual image height instead of using 68, but this works...

Related

Setting constraints in a UITableView cell, keeping UIButtons appropriately spaced

this is something I have a problem with. I have done a bunch of tutorials on constraints in the interface builder and I understand pinning. My app below uses a UITableView and in the UITableView cells there are 4 UIButtons and 4 UILabels. I want to keep the spacing even like below for larger screen sizes. I guess what I mean is I want the spacing to dynamically increase with the screen size but the size of the images remains the same. If I try pinning the left and right UIButtons to their respective edges of the container this distance will not dynamically increase and there will be a big gap in the centre. How can I set it up so the layout is the same that for the smaller screen size?
You can add some transparent views to the superview, and use them as spacers. I thought this was weird at first, but I noticed that Apple recommends it (it even turns up as a suggestion in one of their amazingly informative NSLayout debug messages)
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:
#"H:|[spacer1][view1]\
[spacer2(==spacer1)][view2]\
[spacer3(==spacer1)][view3]\
[spacer4(==spacer1)][view4]\
[spacer5(==spacer1)]|"
options:NSLayoutFormatAlignAllTop
metrics:0
views:viewsDictionary]];
The trick, as you see, is to set each spacer's width as equal to the first spacer. Then you will get even distribution of your visible views. In you case, you would add each of your button/image combos to a container view (view1 ... view4).
In the storyboard...
The blue views represent the (transparent) spacer views. This is the setting to get them to resizeable equal widths. You should also set the visible button/imageview combos to fixed widths.
Although this example says 'add two constraints' it actually adds four, all marked as 'equal width to view' - but it seems to do the right thing. You will also want to set the space between each view ('spacing to nearest neighbour') to zero.

Fluid vertical layout, dynamic labels and autolayout

I am trying to replicate a fluid vertical layout with autolayout in ios7.
The problem is that i am using a stack of UILabels (and others elements) with AttributedStrings (but this seems to not to interfere with the issue since it also comes with plain text) and the text in the labels is added dynamically, but the frame, or better the constraints don't seems to adapt in a correct way automatically (not specifying a height for the labels) since the label remain of the dimensions given in the xib.
Initial configuration in the Xib, blue are setted equality constraints
Same if i use to set a big height constraint constant with value lower of the compression and hugging priority. Nothing change.
After adding text and resizing constraint value (the text should have
5 rows
I finally tryed recreating the contraint every time the text change either this way or with CoreText or many others methods
[self.view setAutoresizesSubviews:NO];
label.lineBreakMode = NSLineBreakByWordWrapping;
label.numberOfLines = 0;
label.preferredMaxLayoutWidth = label.frame.size.width;
[label removeConstraint:oldConstraint];
NSInteger height = ceil([label.attributedText
boundingRectWithSize:CGSizeMake(label.frame.size.width, 10000)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
//TODO 20 or 1.5 is an hardoced value. find solution to workaround
//attributes:#{NSFontAttributeName:[UIFont fontWithName:kFontCrimsonText_Roman size:17]}
context:nil].size.height) + 1;
NSLayoutConstraint * newConstraint = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:height];
[label addConstraint:newConstraint];
This time the dimensions of the labels seems correct but the text inside it is not displayed properly:
Lets say i have a text of 5 rows only the first are shown and the lasts are just cutted despite the fact the the frame seems to be perfectly fitting the required dimensions.
Multiplying the height by 2 (or another constant) seems to fix the problem but obviously now the text is more little than the frame and a lot of blank space remain on top and bottom.
Trying with [label fitToSize] seems to works perfectly, but obviously the constraints bring the frame back right after.
What is the best practice to achieve a vertical fluid layout with dynamic UIlabels and autolayout?
The best way to create a "fluid vertical layout"... UITableView.
Seriously, this looks like a perfect candidate for using a UITableView.
It is useful for more than just displaying lists of data. For instance in the Contacts app in iOS the individual contact (where you can add numbers and emails etc...) uses a UITableView to layout the interface.
It takes all of the bother out of having to calculate heights and positions of labels etc...
Define a custom UITableViewCell with the correct layout of one of your UILabels and then you just have to put the right text into the right row.
UITableView is a really powerful tool for things like this.
I ended up finding this was a bug, due to use of AttributedText in a UILabel with Autolayout turned on, in ios7.
A temporary workaround as sugested in the other question was to append a new-line character to the attributed string and reducing "bottom margins".
Another one is tu use textview.
Bug reported also in:
- Lines missing from tall UILabel when embedding NSTextAttachment
- iOS 7 BUG - NSAttributedString does not appear

Why is my UIButton ignoring its autolayout constraint to stretch its height?

I have a button whose image is set dynamically at run time. As a result, to maintain the appropriate ratio, I have the following code in where the image is set:
[super removeConstraint:self.ratioConstraint];
float ratio=photo.size.height/photo.size.width;
self.ratioConstraint=[NSLayoutConstraint constraintWithItem:self.button
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.button
attribute:NSLayoutAttributeWidth
multiplier:ratio
constant:0];
[self.ratioConstraint setPriority:UILayoutPriorityDefaultHigh];
[super addConstraint:self.ratioConstraint];
I even applied a [self layoutSubviews] to let me check the frame. When I queried the frame, it was right. The ratio of 1 was honored and the height was set equal to the width.
But in ViewWillAppear, the same query produces the same impossible results: the height of the frame is stretching to let the image grow to it's full height. Despite having the above constraint, which should limit the height based on the existing (and honored) width limits.
Even more infuriating, until I set the image to something new at runtime (i. e. by taking the camera), the restraint appears to be functional -- the placeholder image is squashed down horizontally, and compacts itself vertically to fit. It's only once I take a photo that that height explodes, ignoring it's constraint for no apparent reason.
In case it matters, the above code is executed in a custom subclass of UIControl, which is then embedded in a UIView ('ContentView') which is itself embedded in a scroll view. The width restrictions are, more or less:
"ContentView" has a width equal to the ViewController's view.
The UIControl view then has it's width set to a value that either gives it about half or about one quarter of the screen's width, depending on the exact control. (I have five of them; one gets half the screen width, the others all get one quarter and are arranged in a row).
The UIControl then sets the UIButton's width equal to it's own via the constraint: H:|-(0)-[Button]-(0)-| .
Even more annoyingly, I ran through and inspected every constraint from the UIViewController's view down -- a bit of recursive logic that pulled up every constraint in a given view and it's subviews that applied to a given subject. I then ran that against each object in the view hierarchy, and got... zip. No constraints that were interfering. Nothing that should have effected the height of hte buttons in question except the code above, which apply the ratio constraint.
The solution actually proved to be rather embarrassingly obvious, though I'm not sure I understand why the solution worked (or the code above didn't).
In the code above, I used the constant 'UILayoutPriorityDefaultHigh'; in my init code, I used the constant 'UILayoutPriorityRequired'.
Apparently, the photo's desire to stretch out to it's full size is a high priority, so I could only override that 'silent' constraint by upping my own to required. (Interestingly, the bug actually existed before I added the priority calls at all, suggesting that a constraint doesn't start out with that priority -- something I suspect may confuse more than one programmer out there when they trip over it).

UILabel sizeToFit doesn't work with autolayout ios6

How am I supposed to configure programmatically (and in which method) a UILabel whose height depends on its text? I've been trying to set it up using a combination of Storyboard and code, but to no avail. Everyone recommends sizeToFit while setting lineBreakMode and numberOfLines. However, no matter if I put that code in viewDidLoad:, viewDidAppear:, or viewDidLayoutSubviews I can't get it to work. Either I make the box too small for long text and it doesn't grow, or I make it too big and it doesn't shrink.
Please note that in most cases Matt's solution works as expected. But if it doesn't work for you, please, read further.
To make your label automatically resize height you need to do following:
Set layout constrains for label
Set height constraint with low priority. It should be lower than ContentCompressionResistancePriority
Set numberOfLines = 0
Set ContentHuggingPriority higher than label's height priority
Set preferredMaxLayoutWidth for label. That value is used by label to calculate its height
For example:
self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;
[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];
NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:#"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[descriptionLabel_(220#300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
Using Interface Builder
Set up four constraints. The height constraint is mandatory.
Then go to the label's attributes inspector and set number of lines to 0.
Go to the label's size inspector and increase vertical ContentHuggingPriority and vertical ContentCompressionResistancePriority.
Select and edit height constraint.
And decrease height constraint priority.
Enjoy. :)
In iOS 6, using autolayout, if a UILabel's sides (or width) and top are pinned, it will automatically grow and shrink vertically to fit its contents, with no code at all and no messing with its compression resistance or whatever. It is dead simple.
In more complex cases, just set the label's preferredMaxLayoutWidth.
Either way, the right thing happens automatically.
Although the question states programmatically, having encountered the same problem, and preferring to work in Interface Builder, I thought it might be useful to add to the existing answers with an Interface Builder solution.
The first thing is to forget sizeToFit. Auto Layout will handle this on your behalf based upon the intrinsic content size.
The problem therefore is, how to get a label to fit it's content with Auto Layout? Specifically - because the question mentions it - height. Note that the same principles apply to width.
So let's start with an example UILabel that has a height set to 41px high:
As you can see in the screen grab above, "This is my text" has padding above and below. That is padding between the UILabel's height, and it's content, the text.
If we run the app in the simulator, sure enough, we see the same thing:
Now, let's select the UILabel in Interface Builder, and take a look at the default settings in the Size inspector:
Note the highlighted constraint above. That is the Content Hugging Priority. As Erica Sadun describes it in the excellent iOS Auto Layout Demystified, this is:
the way a view prefers to avoid extra padding around it's core content
For us, with the UILabel, the core content is the text.
Here we come to the heart of this basic scenario. We have given our text label two constraints. They conflict. One says "the height must be equal to 41 pixels high". The other says "hug the view to it's content so we don't have any extra padding". In our case, hug the view to it's text so we don't have any extra padding.
Now, with Auto Layout, with two different instructions that say do different things, the runtime has to choose one or the other. It can't do both. The UILabel can't be both 41 pixels high, and have no padding.
The way this is resolved, is by specifying priority. One instruction has to have a higher priority than the other. If both instructions say different things, and have the same priority, an exception will occur.
So let's give it a go. My height constraint has a priority of 1000, which is required. Content hugging height is 250, which is weak. What happens if we reduce the height constraint priority to 249?
Now we can see the magic start to happen. Let's try in the sim:
Awesome! Content hugging achieved. Only because height priority 249 is less than content hugging priority 250. Basically, I'm saying "the height I specify here is less important than what I've specified for the content hugging". So, the content hugging wins.
Bottom line, getting the label to fit the text can be as simple as specifying the height - or width - constraint, and correct setting that priority in association with that axis' content hugging priority constraint.
Will leave doing the equivalent for width as an exercise for the reader!
Noticed in IOS7 sizeToFit wasn't working also - perhaps the solution may help you too
[textView sizeToFit];
[textView layoutIfNeeded];
Another option for ensuring the label's preferredMaxLayoutWidth is kept in sync with the label's width:
#import "MyLabel.h"
#implementation MyLabel
-(void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
// This appears to be needed for iOS 6 which doesn't seem to keep
// label preferredMaxLayoutWidth in sync with its width, which
// means the label won't grow vertically to encompass its text if
// the label's width constraint changes.
self.preferredMaxLayoutWidth = self.bounds.size.width;
}
#end
I feel I should contribute as it took me a while to find the right solution:
The goal is to let Auto Layout do its work without ever calling sizeToFit(), we will do this by specifying the right constraints:
Specify top, bottom, and leading/trailing space constraints on your UILabel
Set the number of lines property to 0
Increment the Content Hugging Priority to 1000
Lower the Content Compression Resistance Priority to 500
On your bottom container constraint, lower the priority to 500
Basically, what happens is that you tell your UILabel that even though it has a fixed height constraint, it can make break the constraint to make itself smaller in order to hug the content (if you have a single line for example), but it cannot break the constraint to make it larger.
In my case I was creating a UIView subclass that contained a UILabel (of unknown length). In iOS7 the code was straightforward: set the constraints, don't worry about content hugging or compression resistance, and everything worked as expected.
But in iOS6 the UILabel was always clipped to a single line. None of the answers above worked for me. Content hugging and compression resistance settings were ignored. The only solution that prevented clipping was to include a preferredMaxLayoutWidth on the label. But I did not know what to set the preferred width to, as the size of its parent view was unknown (indeed, it would be defined by the contents).
I finally found the solution here. Because I was working on a custom view, I could just add the following method to set the preferred layout width after the constraints had been calculated once, and then recalculate them:
- (void)layoutSubviews
{
// Autolayout hack required for iOS6
[super layoutSubviews];
self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
[super layoutSubviews];
}
I added UILabel programmatically and in my case that was enough:
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
i have solved with xCode6 putting "Preferred Width" to Automatic and pin the label top, leading and trailing
UIFont *customFont = myLabel.font;
CGSize size = [trackerStr sizeWithFont:customFont
constrainedToSize:myLabel.frame.size // the size here should be the maximum size you want give to the label
lineBreakMode:UILineBreakModeWordWrap];
float numberOfLines = size.height / customFont.lineHeight;
myLabel.numberOfLines = numberOfLines;
myLabel.frame = CGRectMake(258, 18, 224, (numberOfLines * customFont.lineHeight));
I ran into this problem as well with a UIView subclass that contains a UILabel as one if its internal elements. Even with autolayout and trying all of the recommended solutions, the label just wouldn't tighten its height to the text. In my case, I only ever want the label to be one line.
Anyway, what worked for me was to add a required height constraint for the UILabel and set it manually to the correct height when intrinsicContentSize is called. If you don't have the UILabel contained in another UIView, you could try subclassing UILabel and provide a similar implementation by first setting the height constraint and then returning
[super instrinsicContentSize]; instead of [self.containerview intrinsiceContentSize]; like I do below which is specific to my UIView sublass.
- (CGSize)intrinsicContentSize
{
CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
return [self.containerView intrinsicContentSize];
}
Works perfectly now on iOS 7 and iOS 8.
A solution that worked for me; If your UILabel has a fixed width, change the constraint from constant = to constant <= in your interface file
In my case when using the labels in a UITableViewCell, the label at would resize but the height would overrun the table cell height. This is what worked for me. I did according to Max MacLeod, and then made sure cell height was set to UITableViewAutomaticDimension.
You can add this is in your init or awakeFromNib,
self.tableView.rowHeight = UITableViewAutomaticDimension.
In the storyboard, select the cell, open the Size inspector, and make sure row height is set to "Default" by uchecking the 'Custom' checkbox.
There is an issue in 8.0 that also requires it to be set in code.

Fixed height constraint on UIView

I have a UIView inside of my layout in order to do some clipping and grouping, however the autolayout resizes it when shrunk. I want to give it a fixed height but the only option is to set the top and bottom space.
Is there a way to set an explicit height constraint?
I just found this answer (Auto-Layout Constraint: How to make a view maintains its width/height ratio when resized?). This is what my implementation looks like:
- (void)awakeFromNib
{
[super awakeFromNib];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:self.frame.size.height];
[self addConstraint:con1];
}
Yes and No. In AutoLayout you cannot set a constraint for the size of a UIView, however you CAN prevent a UIView from being "compressed" past its intrinsic size.
This will effectively constrain a view, while avoiding the danger of having a parent view force a child view to be of a certain size (and thus remove the 'Auto' part of AutoLayout).
To set these priorities, you use: setContentCompressionResistancePriority:forAxis:
From the Apple UIView Documentation:
Custom views should set default values for both orientations on creation, based on their content, typically to NSLayoutPriorityDefaultLow or NSLayoutPriorityDefaultHigh. When creating user interfaces, the layout designer can modify these priorities for specific views when the overall layout design requires different tradeoffs than the natural priorities of the views being used in the interface.
Subclasses should not override this method.
Ok, so now we know how to assign a priority to avoid our view getting smaller than its intrinsic size, but how do we set the intrinsic size?
Well, if you are using a standard UI element, you are already set! But if your UIView is custom you will need to override - (CGSize)intrinsicContentSize to return the correct size. In here you can measure any sub-views that view has to calculate the correct dimensions - or if you are using artwork / constant sized elements you can return a hard-codded value.
Again, from the Apple UIView Documentation:
Custom views typically have content that they display of which the layout system is unaware. Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.
Apple strongly advises against inspecting anything outside of your UIView (like getting the size of your super view and tweaking that) as that's not what AutoLayout is for (and can cause bad headaches down the road).
Select your view in the Inspector on the left of Interface Builder.
Ctrl drag a line from the view to itself.
=> A popover appears where you can set explicit height and width constraints.
Swift code of eckyzero code.
let heightConstraint = NSLayoutConstraint(item: yourView,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1, constant: 80)//your desired height
yourView.addConstraint(heightConstraint)
Yes, you can give it a fixed height.
You need to specify at least two constraints in each dimension, so you can't delete the top or bottom space until you've added the fixed height constraint. In IB, select your view, then in the bottom right of the view editor there's a button with three icons, press the middle one and select 'Height'. This adds the height constraint. Now you can go and delete the bottom or top space and you can edit the height constraint if you need to by clicking on it in the size inspector.
You don't need to give it a top space and bottom space. If you have a bottom constraint of say 8 pixels from the bottom of the superview, it will always try to resize the child view to stay within the super view (depending if you set priorities).
You could give the height constraint a priority higher then the bottom space constraint, so when they are in conflict, it will choose to enforce the height constraint and ignore the bottom constraint.
For constraints, you do not need 4 constraints (top, bottom, left, and right); you only ever need 2: vertical and horizontal. The rest is figured by autolay out.
Just remove the bottom constraint and the child view should get clipped.
try this
view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;

Resources