I have a UITableView with Auto Layout and I need to reduce the height when the GADBannerView appears at the bottom of the screen.
Unfortunately with Auto Layout it is impossible to modify the frame. The solution is to remove Auto Layout and set the frame manually. This is very dangerous because all my apps works fine on 3.5' and 4.0' displays and removing Auto Layout adds a new testing phase and more effort.
Is there a way to change the frame even if Auto Layout is enabled?
Let your UITableView constraints to bottom layout is set to 0, make an IBOutlet. Now let your GADBannerView height is 40 so change your outlet.constant = 40; For more about how to make IBOutlet and change its value have a look into this or this hope this will help.
Edit: For those who seeking for example, follow these simple steps (Because this is accepted answer, I think it is worth to have an example. Credit to #manujmv for this example)
Create a height constraint for your view in your interface.
Then add an IBOutlet object in your class for this constraint.
For example:
#property (weak, nonatomic) IBOutlet NSLayoutConstraint
*heightConstraint;
Connect this object in your connection panel.
Then change the value of this constraint whenever you needed
self.heightConstraint.constant = 40;
Rather than trying to change the frame of the view, add a height constraint using auto layout and reduce the value of this constraint. Do the below steps:
create a height constarint for your view in your interface.
Then add an IBOutlet object in your class for this constraint. for example,
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;
Connect this object in your connection panel.
Then change the value of this constraint whenever you needed
self.heightConstraint.constant = 40;
One more thing, you have to call [self.view layoutIfNeeded]; method once you changne the constraints.
Enjoy :)
You can find out constraint like that
extension UIView {
var heightConstaint: NSLayoutConstraint? {
get {
for constraint: NSLayoutConstraint in constraints {
if constraint.firstAttribute == .height {
if constraint.relation == .equal {
return constraint
}
}
}
return nil
}
set{
setNeedsLayout()
}
}
}
Related
I made a special test app for this case. (I'm sorry it is already removed)
I added a view on my controller's view in Storyboard, set up AutoLayout constraints in Interface Builder and made one of them (vertical space) is defferent for different size classes. Screenshot from IB
So the value is 100 for Any height, Any width and 0 for Regular height, Regular width.
It works well, on iPhone vertical distance from top is 100, when on iPad it is 0.
Also I made IBOutlet for this constraint and want to change it in runtime to 10
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;
it seemed I couldn't change it because it gives no effect
- (void)viewDidLoad
{
[super viewDidLoad];
self.topVerticalConstraint.constant = 10; // it doesn't work
}
Although it works when I remove value for Regular height, Regular width in Interface Builder.
Am I miss something about the size classes?
The problem is that constraints are not fully defined yet until Layout events happen between -viewWillLayoutSubviews and -viewDidLayoutSubviews where all the parameters from IB comes into play.
My rule of thumb is:
if you use frames to position your views manually you can do it as early as -viewDidLoad,
if you use autolayout constraints for positioning, make adjustments as early as -viewDidLayoutSubviews;
The second statements only considers code adjustments to constraints that have been made in IB. Adjustments that you are making in -viewDidLoad will be overridden by parameters set in IB during layout. If you add constraints with code you can set them in -viewDidLoad, since there will be nothing to override them.
I've changed your code a bit and it works:
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;
#property (weak, nonatomic) IBOutlet UIView *square;
#property (assign, nonatomic) BOOL firstLayout;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.firstLayout = YES;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.firstLayout) {
self.topVerticalConstraint.constant = 10;
self.firstLayout = NO;
}
}
#end
Notice that -viewDidLayoutSubviews is called many times during the lifetime of a ViewController, so you have to make sure that your adjustments happen only once on initial load.
The problem:
If you set up different value for different size classes in IB for the constraint like this:
then you can't change constant value in code like this:
self.adHeightConstraint.constant = 0; // value set to 0
[self.view layoutIfNeeded]; // value get back to IB value (44 or 36)
In this situation you may see that your constant value persists only until views recalculates. So, after [self.view layoutIfNeeded] the value of constant reset back to whatever was set in IB.
The solution:
Add the second constraint of the same attribute (in my case it was the height) with desired value. You may set this value in IB or change it in the code.
Set low priority for this new constraint. Since it's low priority, it won't be any conflict.
Now when you need to apply the new constant, simple disable the first constraint:
self.adHeightConstraint.active = NO;
[self.view layoutIfNeeded];
I have experienced the same issue, but it doesn't seem to have anything to do with viewDidLoad vs viewDidLayoutSubviews. Interface Builder can manage alternate constraint constants for different size classes, but when you try to update NSLayoutConstraint.constant in code, that constant isn't associated with any particular size class (including the active one).
From Apple docs, Changing Constraint Constants for a Size Class (XCode 7, Interface Builder)
My solution has been to remove the alternate constants from IB, and manage the size-based constraint constant switch in code, only for those specific constraints that are updated/modified in code. Any constraints that are only managed via storyboard/IB can use the alternate-size constants as normal.
// XCode 7.0.1, Swift 2.0
static var isCompactHeight : Bool = false;
static var heightOffset : CGFloat {
return (isCompactHeight ? compactHeightOffset : regularHeightOffset);
}
/* applyTheme can be called as early as viewDidLoad */
func applyTheme() {
// This part could go wherever you handle orientation changes
let appDelegate = UIApplication.sharedApplication().delegate;
let window = appDelegate?.window;
let verticalSizeClass = window??.traitCollection.verticalSizeClass ?? UIUserInterfaceSizeClass.Unspecified;
isCompactHeight = (verticalSizeClass == UIUserInterfaceSizeClass.Compact);
// Use heightOffset
changingConstraint.constant = heightOffset;
}
I'm hoping that some later version of Swift/XCode introduces getters & setters that take size-based alternates into account, mirroring the functionality that's already available via IB.
I check same scenario in sample project it was working may be you are forget to connect NSLayoutConstraint topVerticalConstraint with storyboard.
Change constraint's constant in viewDidLayoutSubviews
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (IS_IPHONE4) {
self.topConstraint.constant = 10;
self.bottomButtonTop.constant = 10;
self.pageControlTopConstraint.constant = 5;
}
}
Easiest solution:
if (self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular && self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
// for iPhone
cnicTopConstraint.constant = -60;
} else {
// for iPad
cnicTopConstraint.constant = -120;
}
I have created the IBOutlet for the constraint of top spacing . I need to update the value that constraint programatically in viewDidLoad. Here is my declaration in IBOutlet :
IBOutlet NSLayoutConstraint *labelTopSpace;
and here is how I changing the top:
labelTopSpace.constant = 50.0;
but this is not working in my case . am I missing anything ?
Update : should I make the property of it ?
Hope, you have mentioned all the constraints under method!
- (void)updateViewConstraints
{
[super updateViewConstraints];
_labelTopSpace.constant = 50;
}
Try using below code..
[btn setConstraintConstant:50.0 forAttribute:NSLayoutAttributeTop];
you can replace the btn with any Ref which for which you have that Constraint that need to modified.{say self.view//if it is for view}
In this case you no need to create property or IBOulet reference to your Constraint..
You can Directly change as above..
Hope it is useful to you as alternate solution...!
Make it property, like
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *labelTopSpace;
then check outlet connection. similarly asked question Unable to make outlet connection to a constraint in IB
I would like to animate, in code, a constraint that is created in IB. I want to find the constraint between the bottom LayoutGuide and the bottom edge of my UIView.
The constraint instances are remarkably opaque: firstItem and secondItem are AnyObject so there is a lot of casting. And apart from doing a string compare on _stdlib_getTypeName(), it's hard to see how I will decide which constraints involve the LayoutGuides.
Or should I just delete all constraints and re-create them on the fly? (But then what's the point of IB? Since my storyboard uses auto layout, I am obliged to add constraints in IB anyway.)
Click on the constraint in Interface Builder and create an IBOutlet for it, just as you would for a button, text view, etc.
You can also find it at runtime using something like this:
NSLayoutConstraint *desiredConstraint;
for (NSLayoutConstraint *constraint in putYourViewHere.constraints) {
if (constraint.firstAttribute == NSLayoutAttributeHeight) { // Or whatever attribute you're looking for - you can do more tests
desiredConstraint = constraint;
break;
}
}
This should be simple. As has already been stated, create an IBOutlet for the constraint you want to animate:
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *verticalSpaceLayoutConstraint;
Then when you want to animate it (Also see: How do I animate constraint changes?) force a layout pass if needed in your view that has the constraint, update your constraint to value you want, do your UIView animation and force layout passes as needed for the animation's duration:
- (void)moveViewSomewhere {
[self.view layoutIfNeeded];
_verticalSpaceLayoutConstraint.constant = 10; // The value you want your constraint to have when the animation completes.
[UIView animateWithDuration:1.0
animations:^{
[self.view layoutIfNeeded];
}];
}
After doing some visual layout in Interface Builder I've created some constraints that I want to access at runtime. Is there a way to label or identify constraints in Interface Builder so they can be looked-up later?
The reason I want to do this is I need to perform some calculation base upon the constraints that are visually specified. I am aware that Apple has provided the Visual Format Language and I could specify the constraints programmatically in a controller. I rather not use this approach so I don't lose the design time feedback of IB.
Edit
Making a referencing outlet did work, but the question still stands.
Update:
As explained by Bartłomiej Semańczyk in his answer, there is now an Identifier field visible in the Attributes Inspector for the NSLayoutConstraint making it unnecessary to expose this field yourself. Just select the constraint in the Document Outline view or in the Storyboard view and then add an identifier in the Attributes Inspector on the right.
Earlier Answer:
Yes, this can be done. NSLayoutConstraint has a property called identifier than can be exposed in Interface Builder and assigned. To demo this, I created a Single View Application that has a single subview that is a red box. This subview has 4 constraints: width, height, centered horizontally in container, centered vertically in container. I gave the width constraint the identifier redBoxWidth by doing the following:
Click on the width constraint in the Document Layout View. Then in the Identity Inspector under User Defined Runtime Attributes, click on the + under Key Path. Change keyPath to identifier, change the Type Boolean to String, and set the Value to redBoxWidth.
Then in ViewDidLoad it is possible to find the constraint by name and change its value:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for subview in view.subviews as [UIView] {
for constraint in subview.constraints() as [NSLayoutConstraint] {
if constraint.identifier == "redBoxWidth" {
constraint.constant = 300
}
}
}
}
}
Since Xcode 7 you can do it in storyboard:
However if you set up your constraint in code, do the following:
let constraint = NSLayoutConstraint()
constraint.identifier = "identifier"
For these constraints you have set up in storyboard, but you must set identifier in code:
for subview in view.subviews {
for constraint in subview.constraints() {
constraint.identifier = "identifier"
}
}
Also you could link constraint to properties of your controller as you do for any other components. Just ctrl drag it into your code:
And then it will be accessible in code of your controller as property:
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *myConstraint;
And you could change it value for example:
self.myConstraint.constant=100.;
Just adding on to #vacawama's answer. You can write a UIView category and pull out constraints using a simple function, as opposed to copy/pasting loops everywhere you need to find a constraint:
.h file:
#import <UIKit/UIKit.h>
#interface UIView (EasyAutolayout)
-(NSLayoutConstraint *)constraintForIdentifier:(NSString *)identifier;
#end
.m file:
#import "UIView+EasyAutolayout.h"
#implementation UIView (EasyAutolayout)
-(NSLayoutConstraint *)constraintForIdentifier:(NSString *)identifier {
for (NSLayoutConstraint *constraint in self.constraints) {
if ([constraint.identifier isEqualToString:identifier]) {
return constraint;
}
}
return nil;
}
#end
Take the IBOutlet of your auto layout constraint.
There is one property called constant in the NSLayoutConstraint class.
For eg, you've taken the IBOutlet of height constraint of any of the views from your IB, and you want to change it's height programmatically, all you need to do is:
constraint.constant = isMoreHeight ? height1 : height2;
After doing this, you need to update all other views in the view hierarchy of the superview. To do this you'll need to write below line:
[self setLayoutIfNeeded];
For better user experience, you can put this line inside your animations block for smoother transition effects,
[UIView animateWithDuration:0.3f animations:^{
[self.view layoutIfNeeded];
}];
Hope this helps..
What about this:
if let myconstraint = self.view.constraints.filter( { $0.identifier == "myconstraintId" }).first {
// TODO: your code here...
}
my two cents for OSX (previous answers deal with UIKit..)
it work for OSX, too:
final private func getConstraintForButton(){
let constraints = self.myButton.constraints
for constraint in constraints {
if constraint.identifier == "redBoxWidth" {
constraint.constant = 300
break
}
}
}
Note: seems NOT working on custom NSView, instead...
I want to change position of UITableview position programmatically. I have added following code in viewDidLoad,ViewWillAppear callbacks, but position is not changing.
Table.frame = CGRectMake(0,100,320,200);
When I disable Auto Layout option in Storyboard, Table View's position is changing. But, I need to keep Auto Layout option to maintain support for different device type.
Use autolayout, and set constraints to your tableView's position & size.
Then, create IBOutlet instances of the type NSLayoutConstraint, and attach them to your constraints in the xib.
Then, in your code, set the values of the constraints to reset the table view position.
Sample code:
in your .h file:
#property IBOutlet NSLayoutConstraint *tableOriginX;
#property IBOutlet NSLayoutConstraint *tableOriginY;
#property IBOutlet NSLayoutConstraint *tableWidth;
#property IBOutlet NSLayoutConstraint *tableHeight;
Then, in your .m file where you want to alter your table's frame:
tableOriginX.constant = 0;
tableOriginY.constant = 100;
tableWidth.constant = 320;
tableHeight.constant = 200;