Can't change UITextView frame size programmatically - ios

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];

Related

Animating a view's height with autolayout when there's no height constraint

I have a view (let's call it "contentView") that its height is determined by its subviews, which is mostly UILabels.
The UILabels content is dynamic and may grow or shrink at runtime.
The UILabels have numberOfLines = 0, and no height constraint, which allow their height to grow with the content.
The UILabels have 0 leading/trailing/top/bottom constraints to their neighbors and/or superview, as follows:
The result is having contentView's height equal the total height of all its sub UILabels (with their height being determined by the height of their text).
I now need to hide contentView on a press of a button (and show it again when the button is pressed again).
Also note, that while animating, if there are other views below "contentView" they need to move in to take the empty space (and move back out when the button is pressed again).
It seems the most natural animation is to change the height of contentView to 0, while animating the views under contentView up at the same time (with the reverse animation changing the height back to its original height).
Any suggestions on how to animate contentView's height?
Unfortunately I was not able to create a smooth enough animation for this layout, but I found a workaround.
Constraints change the layout while animating, and I needed the opposite effect - the layout had to remain fixed while the animation is in progress.
So I disabled the constraints for the duration of the animation, and that seemed to work well.
The full recipe is:
I disabled the constraints on contentView:
contentView.translatesAutoresizingMaskIntoConstraints = YES
I then did a simple frame animation. I made sure to calle layoutIfNeeded on superview to make sure the view that's under contentView moves up during the animation:
CGRect frame = view.frame;
frame.size.height = 0;
[[view superview] layoutIfNeeded];
[UIView animateWithDuration:0.5 animations:^{
view.frame = frame;
[[view superview] layoutIfNeeded];
}];
I also had to change some properties for the subviews to have them animate correctly (this step might not be needed. It depends to the base layout of the subviews):
for UITextView: scrollEnabled = YES
for UIButton: clipsToBounds = YES
At the end of the animation, on the completion block, I set contentView.translatesAutoresizingMaskIntoConstraints back to NO and restored the subviews properties:
for UITextView: scrollEnabled = NO
for UIButton: clipsToBounds = NO
(I ended up using UITextViews instead of UILabels because I needed the text to be selectable. I then set scrollEnabled to NO to size the textviews based on their content).
Yes: add a height constraint with a constant of 0.
You can use deactivateConstraints() to temporarily disable other constraints that might conflict with this, and activateConstraints() to re-enable them.
You could put the relevant constraints in an array programmatically, or use an outlet collection if you're using a storyboard.

translatesAutoresizingMaskIntoConstraints and UILabel

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 :)

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.

Autoresize UIPopoverController using autolayout

I'm trying to use autolayout to automatically resize my popover to fit it's contents. I have fixed popover width but to compute height i rely on systemLayoutSizeFittingSize: passing my predefined width and zero height e.g. CGSizeMake(190, 0).
ContentController* controller = [ContentController new];
CGSize preferredSize = [controller.view systemLayoutSizeFittingSize:CGSizeMake(190, 0)];
controller.preferredContentSize = preferredSize;
UIPopoverController* popover = [[UIPopoverController alloc] initWithContentViewController:controller];
//popover presentation.
So far, so well, my current ContentController view hierarchy is something like (setup in the Interface Builder):
UILabel - multiline header (dynamically resized)
|
UIImage with fixed width / height (static size)
|
UILabel - multiline body (dynamically resized)
Thus, i just plug in my header / body text, call systemLayoutSizeFittingSize and it returns valid size that fits all the content of the view.
The problem arises when i try to put my body label inside UIScrollView. (Both in the IB and in code).
From now on, systemLayoutSizeFittingSize will not take body label height into account and will return height for header + image.
I've setup all the constraints in the IB to support Pure Autolayout approach.
I've checked scrollview's content size - it is indeed equals to body label's intrinsic size, but scroll view's frame is squashed into 0.
I've checked and tried to reset label's maxPreferredLayoutWidth to the width of the content view, but it doesn't seems to affect anything.
I've set translatesAutoresizingMaskIntoConstraints on every view to NO, but it has no effect.
I've set hugging / compression resistance priorities of both label and it's scrollview to 1000, but no luck. Setting them on the container view doesn't work either.
Here are screenshots of my IB view setup:
My guess that is is somehow related to popover hosting views and their autolayout constraints, but i'm not sure.
I'm updating my labels via simple
_textContainerHeaderLabel.text = headerText;
_textContainerBodyTextLabel.text = bodyText;
[self.view setNeedsUpdateConstraints];
[self.view layoutSubviews];
So, the main question - how do i compute view's optimal fitting size via autolayout when it has UIScrollViews in it?
Finally found a solution for this case. Don't know how correct it is, but i did the following - I've added height inequality constraint (>=0) from label to it's scrollview.
The trick is to make this constraint's priority lower than label's compression resistance (vertical, in my case). This seems to to solve this problem.

How do I get auto layout to reposition a view when it's sibling view changes size?

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.

Resources