translatesAutoresizingMaskIntoConstraints and UILabel - ios

I have created this simple custom UIView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
internalView = [UIView new];
[self addSubview:internalView];
internalView.backgroundColor = [UIColor whiteColor];
sublabel = [UILabel new];
sublabel.text = #"test";
sublabel.backgroundColor = [[UIColor yellowColor] colorWithAlphaComponent:0.4];
sublabel.textAlignment = NSTextAlignmentCenter;
sublabel.translatesAutoresizingMaskIntoConstraints = NO;
[internalView addSubview:sublabel];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[internalView setFrame:self.bounds];
[sublabel setFrame:internalView.bounds];
}
#end
The view is used in a xib where are defined 4 constraints:
The constrains are related to the top, trailing, leading and the height.
The views hierarchy of the custom view is self -> internalView -> sublabel
The layoutSubviews is doing a simple thing like setting the frame of each subview with the bounds of the superview.
I created the code above just to point out a strange behaviour i found in translatesAutoresizingMaskIntoConstraints.
(The yellow view is the label)
If the value of the property is YES the results is what I expect from the code in the layoutSubviews method
If it is NO, with the same layoutSubviews i got:
The documentation of the translatesAutoresizingMaskIntoConstraints says:
If this is value is YES, the view’s superview looks at the view’s
autoresizing mask, produces constraints that implement it, and adds
those constraints to itself (the superview).
In both cases the autoresizingMask is set to UIViewAutoresizingNone and there are no constraints in the constraints property of the label.
Why with the translatesAutoresizingMaskIntoConstraints=NO the frame that i set in the layoutSubviews is not what i see on screen?
Edit 2:
I expect, given the exposed scenario, to have the same results with translatesAutoresizingMaskIntoConstraints set to YES or NO.
Edit:
I tried to swizzle the sizeToFit method to check if it is called. It's not.
This is happen in iOS6/7
Update 08/08/14
After further investigation i discovered that there is a way to change the frame of the label without having autolayout playing around.
I discovered that after the first pass of layout (when is called the layoutSubviews) with the translatesAutoresizingMaskIntoConstraints=NO autolayout adds constraints for the hugging/compression of the UILabel. The point is that for every view that implements intrinsicContentSize returning something different from UIViewNoIntrinsicMetric the autolayout adds specific constrains. That is the reason behind the resizing of the label.
So the first thing that i did is to reimplement a subclass of the UILabel to override the
intrinsicContentSize.
After that, following the suggestions in this really good article http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html, I tried to turn of autolayout completely for the subviews involved removing [super layoutSubviews].
The goal for me was to avoid that autolayout could act on views where a was trying to apply animated transformations. So if you have the same needs i hope this can help you.

This comes more from intuition of having used it than actual study, but...
If you set translatesAutoresizingMaskIntoConstraints to YES, the system will create constraints to enforce the frame you defined for your view.
If it is set to NO, no constraints will be set for the view, and as you did not set them yourself either, the view will be resized according to default behaviours. In this case it seems to resize to the minimum content size because of the "contentHugging" values.
Bottom line is, from my understanding, when auto-layout is active all views need constraints to be properly placed. You either set that property to YES, or set the constraints yourself. If you don't do either, results will be a bit unpredictable.
The UIViewAutoresizingNone mask is still a valid mask, with fixed dimensions (no resizing) and it will be translated to constraints. Views can coexist without setting constraints, when you set that option to YES.
You are interpreting UIViewAutoresizingNone as meaning "no mask" when it really means "mask with no resizing". Sorry to disagree with you, but I think that this is the expected behaviour :)

Related

Button width and height is disabled in horizontal stackview

Im learning iOS development right now, XCode doesnt allow me to edit width and height of buttons which are in stack view:
In the Storyboard I create a new button of size 30 x 30 with a custom image and then make more 5 copies of that button. Then I embed them after selecting all of them in a Stack View. Now a disaster happens, the buttons are resized to god knows what size and they appear huge and when I try to go to size inspector to resize those buttons I see that "Width" and "Height" fields are disabled.
I tried few suggestions on stackoverflow and selected the stack view and change the distribution of stack view to "Fill Equally" but still the buttons size is being changed. I dont want this to happen. I want a fixed size buttons in a horizontal stack view and putting them in stack view should not change the size or shape of buttons like this. Can anyone please tell me how do I fix this problem?
Please help.
Sometime Interface Builder is not easy to handle because it is a running layout system at design-time / IB_DESIGNABLE. You make changes, IB gets triggered to 'think', changes parameters, layouts again, you see it does not fit and you change again.
It can be easier to fix UIStackView's constrains to your outer layout before dropping content that will be arranged by taking intrinsicContentSize of the subviews into its calculation. Even worse, if the stackview does not have complete constrains already and you drop something in as being arranged, it will take the default size as intrinsicContentSize of the dropped view and change the stackview spacing as it should. This is no surprise but it can be frustrating as convenience is disturbing your workflow here.
The docs tell you should not change intrinsicContentSize because it is not meant to be animated, it will even disturb animations and layout or even break constrains. Well, you can not set intrinsicContentSize, it is read-only. As thats for good reasons they could have written that while UIView's are instanced they can have supportive variables which have to be set before laying out which allows you to make pre-calculations.
While in code this can be tricky also, you can subclass UIView to make arranged subview instances more supportive to your needs.
There is UIView's invalidateIntrinsicContentSize that triggers the layout to take changed intrinsicContentSize into the next layout cycle. You still cant set intrinsicContentSize, but thats not needed when you would have a class designed like shown below.
// IntrinsicView.h
#import UIKit
IB_DESIGNABLE
#interface IntrinsicView : UIView
-(instancetype)initWithFrame:(CGRect)rect;
#property IBInspectable CGFloat intrinsicHeight;
#property IBInspectable CGFloat intrinsicWidth;
#end
// IntrinsicView.m
#import "IntrinsicView.h"
#implementation IntrinsicView {
CGFloat _intrinsicHeight;
CGFloat _intrinsicWidth;
}
- (instancetype)initWithFrame:(CGRect)frame {
_intrinsicHeight = frame.size.height;
_intrinsicWidth = frame.size.width;
if ( !(self = [super initWithFrame:frame]) ) return nil;
// your stuff here..
return self;
}
-(CGSize)intrinsicContentSize {
return CGSizeMake(_intrinsicWidth, _intrinsicHeight);
}
-(void)prepareForInterfaceBuilder {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _intrinsicWidth,_intrinsicHeight);
}
#end
Now this gives you control of the behaviour when UIStackView will layout.
Let's look at instancing of your UIStackView.
#import "IntrinsicView.h"
- (void)viewDidLoad {
[super viewDidLoad];
UIStackView *column = [[UIStackView alloc] initWithFrame:self.view.frame];
column.spacing = 2;
column.alignment = UIStackViewAlignmentFill;
column.axis = UILayoutConstraintAxisVertical; //Up-Down
column.distribution = UIStackViewDistributionFillEqually;
CGFloat quadratur = 30.0;
for (int row=0; row<5; row++) {
IntrinsicView *supportiveView = [[IntrinsicView alloc] initWithFrame:CGRectMake(0, 0, quadratur, quadratur)];
// supportiveView stuff here..
[column addArrangedSubview:supportiveView];
}
[self.view addSubview:column];
}
Don't forget IntrinsicView's intrinsicContentSize is set before instancing is complete, so this example takes frame size at initWithFrame as intended and stores that size to be used when intrinsicContentSize is asked. Having that still needs that UIStackView is large enough to layout nicely but you forced the arranged subviews to that intrinsic size. Btw. the example is arranged up..down.
You can use the IntrinsicView in Interface Builder, just change the views inside UIStackView to the above written class. IB will automatically update the designable API and serve you propertys you can set up. This still needs the StackView to have at least width and height set and also constrains if needed. But it takes away the impression your width and height of arranged views would have any effect other than expected, because IntrinicViews height + width is inactive in IB then.
Just to show you how much this improves your possibilities in IB, see image

UITableViewCell's contentView gets unwanted "height==44" constraint

I'm creating my UI entirely in code and using Masonry to constrain the cell's content view's subviews to the appropriate height. I'm using
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height
on iOS 7 for the row height, while iOS 8 handles it automatically.
Everything looks exactly as it should on screen, but in the console I get trainloads of warnings for conflicting constraints, which all seem to be caused by an unasked and unnecessary height constraint on the cell's content view (e.g. <NSLayoutConstraint UITableViewCellContentView.height == 44>).
On iOS 8 I'm setting the table view's rowHeight as UITableViewAutomaticDimension (effectively -1) but still I get this constraint. I'm only adding constraints between the content view and its own subviews, so no constraints between the content view and the cell itself.
Any idea where this constraint comes from and how to make it go away?
Edit: Now I actually found a "solution" of sorts – initially setting the content view's frame to something ridiculous, like CGRectMake(0, 0, 99999, 99999), before adding subviews or constraints, seems to make the warnings go away. But this doesn't exactly smell like the right way to do it, so can anyone tell of a better approach?
I had the same issue and fixed it setting the auto resizing mask of the cell like this:
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask = .flexibleHeight
}
Also in the controller I set the estimated height and tell the table view to use automatic dimension (in the viewDidLoad method:
self.tableView.estimatedRowHeight = 120
self.tableView.rowHeight = UITableView.automaticDimension
These links helped:
http://useyourloaf.com/blog/2014/08/07/self-sizing-table-view-cells.html
Auto layout constraints issue on iOS7 in UITableViewCell
Hope this helps!
To tack on to the accept answer- after months of trying to get iOS 8's automatic cell sizing to work I discovered an important caveat. The 'estimatedRowHeight' property MUST be set. Either via the tableView directly or by implementing the delegate methods. Even if there's no way to determine a valid estimate, simply providing a value other than the default (0.0) has demonstrably allowed iOS 8's cell layout to work in my testing.
Regarding to the "solution" mentioned in the edit in the question (setting the contentView frame to something big temporarily), here's proof this is a good "solution":
https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8/blob/master/TableViewCellWithAutoLayout/TableViewController/TableViewCell.swift
// Note: if the constraints you add below require a larger cell size than the current size (which is likely to be the default size {320, 44}), you'll get an exception.
// As a fix, you can temporarily increase the size of the cell's contentView so that this does not occur using code similar to the line below.
// See here for further discussion: https://github.com/Alex311/TableCellWithAutoLayout/commit/bde387b27e33605eeac3465475d2f2ff9775f163#commitcomment-4633188
// contentView.bounds = CGRect(x: 0.0, y: 0.0, width: 99999.0, height: 99999.0)
It's hacky but it seems to work.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
//self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.itemView = [CustomItemView new];
[self.contentView addSubview:self.itemView];
}
return self;
}
set translatesAutoresizingMaskIntoConstraints to NO is not work for me
, but autoresizingMask = UIViewAutoresizingFlexibleHeight is well.
you should also making constraints like this:
- (void)updateConstraints {
[self.itemView mas_updateConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.top.equalTo(0);
//make.bottom.equalTo(0);
make.bottom.lessThanOrEqualTo(0);
}];
[super updateConstraints];
}
bottom constraints not just equalTo contentView's bottom, you should use lessThanOrEqualTo
hope this is work to you!
I found out that in some cases, setting an estimatedHeight that is many times bigger the height of my average cell fixed most if not all warnings and had no negative impact on the display.
i.e.:
Setting self.tableView.estimatedRowHeight = 500.0f while most rows are only about 100.0f in height fixed my issue.

translatesAutoresizingMaskIntoConstraints doesn't work

I create a UIButton programmatically and add it to a view like this :
self.button = [[UIButton alloc] initWithFrame:CGRectMake(20, 20, 100, 30)];
[self.button setTitle:#"Test" forState:UIControlStateNormal];
self.button.translatesAutoresizingMaskIntoConstraints = YES;
[myView addSubview:self.button];
The button appears correctly, and it doesn't use its intrinsic size (which is correct because translatesAutoresizingMaskIntoConstraints is on). However, when i print out myView.constraints, i do not see any constraint regarding the button. By definition, when translatesAutoresizingMaskIntoConstraints is on, constraints will be automatically generated and added to the superview, which is myView in this case.
So why no constraints are generated here ? And why the layout system still knows how to layout the button on screen ?
********Updated:
i think i know the reason. When a UIView doesn't have any constraint attached to it, Layout system will not come into place. It will uses the view's frame.
In this case, when we turn on translatesAutoresizingMaskIntoConstraints, the button's intrinsic constraints are not generated, hence the button does not have any constraint. So the frame will be used.
All UIView created from code will have translatesAutoresizingMaskIntoConstraints default to YES, so no constraint is automatically generated for the view. The frame will be use. That makes perfect sense for backward compatibility.
The docs describe its behavior as follows:
If translatesAutoresizingMaskIntoConstraints = YES, the view’s superview looks at the view’s autoresizing mask, produces constraints that implement it, and adds those constraints to itself (the superview).
Based on this description, it is expected to be able to look in the ‑[UIView constraints] array to find the translated constraints, but the generated constraints are not added there When you create view programatically (If view is added in nib then only we will get expected behaviour).
After some careful exploration with the debugger, I determined that the constraints are generated on demand during layout by the private method ‑[UIView(UIConstraintBasedLayout) _constraintsEquivalentToAutoresizingMask].
To View that Constraints create a Category of UIView
#interface UIView(TestConstriants)
- (NSArray *)_constraintsEquivalentToAutoresizingMask;
#end
Import this category to your ViewController class #import "UIView+TestConstriants.h"
Call This lines of code ofter adding self.button to view :
NSLog(#"constraints: %#", [self.button _constraintsEquivalentToAutoresizingMask]);
The above logs the constraints equivalent to autoResize mask.
Try testing by setting autoresizingMask to different values you will get corresponding equivalent constraints.

Can't change UITextView frame size programmatically

I've insert an UITextView in a view with Interface builder, now I would like to change its frame size so it fits the content programmatically. The problem is that the size seems locked and unchangable from code because of the constraints. If I disable in file inspector use auto-layout every object gets the constraints removed, but I only want to change the UITextView not the other objects.
[textview setFrame:CGRectMake(x,y,w,h)]; // This doesn't do anything to the uitextview
If you're using constraints, then you must use constraints to change the UITextView's size. Set up outlets in the nib to get access from your code to the constraints you need to change. You can set a constraint's constant in real time, and this is usually sufficient.
Just to clarify the reasons for this: you cannot change a view's frame if it is being positioned by constraints. Well, you can, but it's fruitless and it's bad practice. This is because the constraints themselves will be used by the layout system to change the view's frame for you! Thus, you can change the frame, but the layout system will then read and resolve the constraints and change the frame back again.
Think of constraints as a "to-do list" for the layout system. Constraints do nothing in and of themselves; they are just a list of rules. It is the layout system that does the work (when layoutSubviews is called). Every time layout is needed, the layout system comes along, reads the constraints, works out how to obey them, and does so - by setting the frames of your views. You need to work with that system, not against it.
All of UIViews and classes are inherited from UIView have same property is: "Lock" in XIB file.
If you want to change frame of those views. You must set Lock is "Nothing"
If you don't care to become a constraint expert, do it like this (in your view controller.)
#property UITextView *textView;
//Create UITextView on the fly in viewWillAppear
rect = CGRectMake(194., 180., 464., 524.);
_textView = [[UITextView alloc] initWithFrame:rect];
[self.view addSubview:_textView];
//Now you can resize it at will
CGRect oldFrame = _textView.frame;
CGRect newFrame = oldFrame;
newFrame.size.height += 300;
[_textView setFrame:newFrame];

Programatic autolayout, constraints and UIView runtime

In reading the View Programming Guide from Apple it is apparent they have not updated it for constraints. So a couple of questions:
Many posts here talk about NOT using -initWithFrame when initializing a new UIView or setting it to CGRectZero or some equivalent. How would you handle this then?:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self){
//Set up rounded rectangle ivar
CGRect frame = [self bounds];
CGFloat radius = frame.size.height / 2;
_rectPath = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:radius];
}
}
and then putting the drawing code in the drawRect:. I have found that it won't init properly without a frame. (Obviously, I'm initing this UIView and immediately setting constraints but no frame immediately). This code set's frame = (0,0,0,0) because constraints haven't been computed yet.
If we don't put this code in init where would we put it? Also, I thought it was good practice to init all our stuff we needed in the view in init.
So this leads to question...
When is -updateConstraints called in the runtime cycle and when does the view compute it's bounds using those constraints? I have noticed that the view really doesn't know what it's bounds are until after -layoutSubviews. Is there another place before this that the UIView knows it's bounds if it has only been set with constraints?
Sorry about all the questions, but they all kinda seem connected. Thanks for the looks.
Your problem with your frames is not related to auto layout. View size changes all the time. It is almost never the same as during object instantiation. You need to create/calculate the size of paths to fit the current size during drawRect, not when you first create the view.
The only objects you should set during init are properties and/or ivars that the rest of your class relies on existing in a certain state.

Resources