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.
Related
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 :)
I have created one UIView inside the UIScrollView from storyboard.
I want to set the frame to UIView programatically. I have tried to set frame using setFrame function programatically, but its not working for the UIView which is inside UIScrollView.
if I create one UIView inside the UIScrollView using [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 110)],and than change size of the view,it is work!
my code:
but I want to create a uiview by IB and set frame using setFrame function.
Most impornatant condition is Autolayout is "TRUE".
Most impornatant condition is Autolayout is "TRUE".
If you want to mix constraints with setting a view's frame directly, you will need to make sure to set view.translatesAutoresizingMaskIntoConstraints = YES for your view. AFAIK, this cannot be done in IB for iOS, only in code.
Also beware that setting translatesAutoresizingMaskIntoConstraints = YES will add new constraints to those you define in IB (or programmatically) and you have to ensure they remain consistent, otherwise you will get an exception. So, e.g., if you set the frame origin in code, but also pinned the top distance between your view and the superview, you will run into problems if those specifications are not compatible.
I have a view controller with two views (greenView, redView) and a switch:
I have a constraint on the redView top space of 50px from greenView. Here are all the constraints on redView:
I have a switch that when tapped toggles the height of greenView from 50px to 100px.
When I launch the app, the views are laid out as I want, but when the switch is tapped, the greenView frame changes from a height of 50 to 100, the redView doesn't do what I would expect - which is to shift it's y position 50px down to maintain the top space constraint it was assigned. I have an inequality constraint put on the bottom space of redView so there are no conflicts, and I am also calling [self.redView layoutIfNeeded].
Here's the relevant code:
- (IBAction)switchTapped {
if (theSwitch.isOn) {
greenView.frame = CGRectMake(0, 30, 320, 100);
[redView layoutIfNeeded];
} else {
greenView.frame = CGRectMake(0, 30, 320, 50);
[redView layoutIfNeeded];
}
Why isn't redView's y position being updated?
I have watched WWDC videos, but they seem to mention calling layoutIfNeeded in passing. What am I missing?
Here's what it looks like when launched:
And here it is when the switch is tapped:
A couple of pointers:
layoutIfNeeded assumes that you have previously called setNeedsLayout. Use setNeedsLayout instead and your app will update the layout as soon as appropriate.
Setting a frame generally does not work with autolayout. Set or change a height constraint instead. This is something you should do in code, mros had a good suggestion for this.
I find that inequality constraints are usually not the best solution, since they often lead to ambiguous layout. Instead, set an equality constraint, but lower the priority (e.g. to 500). The autolayout system will try to honor the constraint as much as possible, but will prioritize other constraints. (By default, all constraints have 1000 priority.)
There are a couple things that are causing problems. The first thing is that autolayout is supposed to ignore the object's frame. If you want to change the height of something, you change its height constraint, not the frame height. This is because constraints are applied after your layout and would override any frame changes. In addition, the red view looks like it is constrained to your superview "view" not "greenView". As far as editing the constraints, you can set up an IBOutlet and then modify the constant property of the UILayoutConstraint object.
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];
I have a UIButton and it can change the title at the runtime. Therefore, I want to increase the UIButton height depend on the title text for display full text by using AutoLayout.
I can increase the UILabel height by set the height constraint to "Greater than or Equal" but it not work with UIButton.
I have used [myButton sizeToFit] but it only increase the UIButon width (not increase height).
My current UIButton properties now is
- constraint height: 30
- leading : 15
- trailing: 15
- top: 5
- fontsize: 12
UPDATE
I created an IBOutlet for constraint height of UIButton for changing the height as #NSNood said.
Then I need to use \n in title text to split line.
But I don't know where should I put the \n?
Here is the Button that I want in portrait
Here is the Button that I want in landscape
How can I determine the place to put \n?
Please guide me how to achieve it with AutoLayout. Any help would be appreciated.
Sorry that I didn't follow the post, lately and thus am coming up with a real late solution. Still I'm writing the answer as a reference, if someone might find it useful in future.
First of all let's show the storyboard configuration for the button. Those are depicted in the following pictures:
The picture shows that I have added only left, top and right constraints for the button and nothing else. This allows the button to have some intrinsicContentSize for it's height but it's width is still determined by it's left and right constraints.
The next phase is to write some ViewController class that shall contain the button. In my VC, I have created an outlet for the button by name button:
#property(nonatomic,weak) IBOutlet UIButton* button;
and has attached it to the storyboard button. Now I have overridden two methods, namely, viewDidLoad and viewWillLayoutSubviews like below:
-(void)viewDidLoad {
[super viewDidLoad];
self.button.titleLabel.numberOfLines = 0;
self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
-(void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self.button setTitle:#"Chapter One\n "
"A Stop on the Salt Route\n "
"1000 B.C.\n "
"As they rounded a bend in the path that ran beside the river, Lara recognized the silhouette of a fig tree atop a nearby hill. The weather was hot and the days were long. The fig tree was in full leaf, but not yet bearing fruit." forState:UIControlStateNormal];
}
The viewDidLoad method ensures the titleLabel (the label that
holds button text) is multiline and if some large text comes to it,
it wraps the text by wrapping words.
The viewWillLayoutSubviews method ensures button layouting process
occurs whenever bounds of the main view change, e.g. due to the
change of interface orientation.
The final and the most effective part is to manually handle the layout process for the button. For this purpose, we need to subclass UIButton. I have written a subclass named MyButton that inherits from UIButton and you might use whatever name you like. Set this as the custom class for the button in Identity Inspector.
The subclass overrides two methods, namely, intrinsicContentSize and layoutSubviews. The class body looks something like the following:
#import "MyButton.h"
#implementation MyButton
-(CGSize)intrinsicContentSize {
return [self.titleLabel sizeThatFits:CGSizeMake(self.titleLabel.preferredMaxLayoutWidth, CGFLOAT_MAX)];;
}
-(void)layoutSubviews {
self.titleLabel.preferredMaxLayoutWidth = self.frame.size.width;
[super layoutSubviews];
}
#end
The UIButon subclass takes the ownership of the layout process by overriding layoutSubviews method. The basic idea here is to determine the button width, once it has been layout. Then setting the width as preferredMaxLayoutWidth (the maximum width for layouting engine, that a multiline label should occupy) of it's child titleLabel (the label that holds button text). Finally, returning an intrinsicContentSize for the button based on it's titleLabel's size, so that the button fully wraps it's titleLabel.
The overridden layoutSubviews is called when the button is already
layed out and it's frame size is determined. At it's first step,
button's rendered width is set as preferredMaxLayoutWidth of the
button's titleLabel.
The second step re-invokes the layouting engine by calling [super
layoutSubviews], so that the buttons intrinsicContentSize is
re-determined based on it's titleLabel's
preferredMaxLayoutWidth, which is set to buttons rendered width,
by now.
In the overridden intrinsicContentSize method we return the
minimum fitting size for the button that fully wraps it's
titleLabel with preferredMaxLayoutWidth set. We use
sizeThatFits fits method on the button's titleLabel and that
simply works as titleLabel doesn't follow any constraint based
layout.
The outcome should be something similar to that you might have required.
Feel free to let me know about any other clarification/concern.
Thanks.
Ayan Sengupta solution in Swift, with support for contentEdgeInsets (thanks Claus Jørgensen):
(You may also further customize the code to take titleEdgeInsets into account if needed)
Subclass your UIButton to take the ownership of the layout process:
/// https://stackoverflow.com/a/50575588/1033581
class AutoLayoutButton: UIButton {
override var intrinsicContentSize: CGSize {
var size = titleLabel!.sizeThatFits(CGSize(width: titleLabel!.preferredMaxLayoutWidth - contentEdgeInsets.left - contentEdgeInsets.right, height: .greatestFiniteMagnitude))
size.height += contentEdgeInsets.left + contentEdgeInsets.right
return size
}
override func layoutSubviews() {
titleLabel?.preferredMaxLayoutWidth = frame.size.width
super.layoutSubviews()
}
}
Use this class in your storyboard, and set constraints for Leading, Trailing, Top, Bottom. But don't set any Height constraint.
An alternative without subclassing is to add a wrapper view as suggested by Bartłomiej Semańczyk answer and Timur Bernikowich comment.
The point is that if you set sizeToFit property, then the text will always be in one line and the width of the button will increase unless you put a next-line sign \n to explicitly say that you want it to be several lines.
You put '\n' in the end of the first line like "line \n line" which represents
line
line
If you want to have two different string values (with \n positioned differently) for Portrait and Landscape you can check the orientation condition using UIDeviceOrientation (UIDevice.currentDevice.orientation) described here and set a string value depending on the orientation of the device
There is a way I always used:
Add another reference UILabel which lineNumber=0 and the same width with the target button.
Do not set height constraint for the ref-UILable, and should set a height constraint for the button to adjust its height
Set the same text to the ref UILabel with the button.titleLable, sizeTofit it and get its frame.size.height
Use the height value to the height constraint of the target button. (Of course, the button.titleLabel linenumber should be set to 0 or more lines)
Done. :)
PS1. This way can be used for the button and ref-label in a scrollview.
PS2. In some case, we can not get the correct height of the ref-label because it cannot gain a correct frame.width in scrollview, especially when we use the trailling constraint. We could consider to define a fixed width to the ref-label before sizeTofit and obtain the correct height for target button use.