Change Autolayout at runtime - ios

Is it possible to change autolayout constraints during runtime?
I know you can change the constant, but how would you change different attributes.
For example, NSLayoutAttributeTop to NSLayoutAttributeBottom?
Here is a simple sample of what I hope to achieve, it will set a label top left, then when you press a button it will set the label bottom right.
The initial constraints work as expected, tapping the button doesn't work as expected and throws the infamous "Unable to simultaneously satisfy constraints."
Here is the code I am using:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
self.constraintA = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
self.constraintB = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0.0];
[self.view addConstraint:self.constraintA];
[self.view addConstraint:self.constraintB];
}
- (IBAction)tappedChange:(id)sender
{
[self.view removeConstraints:#[ self.constraintA, self.constraintB ]];
self.constraintA = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
self.constraintB = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0.0];
[self.view addConstraint:self.constraintA];
[self.view addConstraint:self.constraintB];
[self.view setNeedsLayout];
}
Thank you for your time.

You only need to perform the Remove/Recreate/Add constraint dance on iOS 7 and below. If you are writing for a modern iOS (8 and above) you can create all your constraints at once and then just set the .active property on whatever NSLayoutConstraint instance you want at any given time.
// Move to right
self.leadingConstraint.active = false;
self.trailingConstraint.active = true;
// Move to bottom
self.topConstraint.active = false;
self.bottomConstraint.active = true;
If you are using Interface Builder you can create all the constraints that will be needed (note the grayed out constraints that aren't active by default).
Then when the button is pressed you can deactivate the old constraints and activate the new ones.
If you are ever unsure about the views being shown you can pause the app execution and use the following private API in the debugger to print out a full list of views and constraints:
po [[UIWindow keyWindow] _autolayoutTrace]

Personally I like using Masonry to manage constraints in code - it's way less writing, easier to read what you wrote six months ago, and the learning curve isn't as head-splitting either.
For example:
// Define some constraints, making the top one a #property:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];
// Update the top constraint to match the bottom of the superview instead of the top
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_bottom).with.offset(padding.bottom);
}];

Related

iOS Custom Keyboard Extension Autolayout

I'm developing a custom keyboard and i want to use only a UICollectionView as content. The CollectionView should fill (center x and y, width and height available space) of the keyboardview.
My problem is that if i rotate the device the collection view won't be resized.
I tried layout constraints, resizing the view on viewWillTransitionToSize but it does not work. Can anyone give me a hint or show me a way to get this done?
Autolayout should set translatesAutoresizingMaskIntoConstraints to NO before adding Constraint. The code below can make a UICollectionView autolayout with equal edges.
- (void)viewDidLoad {
[super viewDidLoad];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
collectionView.translatesAutoresizingMaskIntoConstraints = NO;
collectionView.backgroundColor = [UIColor redColor];
[self.view addSubview:collectionView];
//Adding layout constraint
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0]];
}
In addition, I recommend you to use Masonry to build autolayout. In your case, you just need to run the code below to make constraints.
[collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
By the way, autolayout will use more memory then calculating frame yourself. As iOS Keyboard extension has a very harsh memory usage limit (around 40mb depends on your device). If your keyboard would use many memory on process other things, I suggest you don't use autolayout.

how to add Constraint in loop using objective c

While using "addConstraint" on UIButton in loop issue is coming with button x-position,
for (int ix = 0; ix<7; ix++) {
UIButton *segmentButton = [[UIButton alloc] init];
[segmentButton setTitle:[_segmentButtonTitleArray objectAtIndex:ix] forState:UIControlStateNormal];
[_segmentView addSubview:segmentButton];
[segmentButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[_segmentView addConstraint:[NSLayoutConstraint constraintWithItem:segmentButton
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:_segmentView
attribute:NSLayoutAttributeWidth
multiplier:1.0/7
constant:0]];
[_segmentView addConstraint:[NSLayoutConstraint constraintWithItem:segmentButton
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:segmentButton.frame.size.height]];
[_segmentView addConstraint:[NSLayoutConstraint constraintWithItem:segmentButton
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:_segmentView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:segmentButtonXposition]];
[_segmentView addConstraint:[NSLayoutConstraint constraintWithItem:segmentButton
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_segmentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:segmentButton.layer.frame.origin.y]];
segmentButtonXposition = segmentButtonXposition +1+_segmentView.frame.size.width/7-1;
}
An the desired result is like that, but unable to produce result.
Desired Result
It sure will take the previous width after rotation. See how you are calculating the left attribute value for constraint; from segmentVIew's frame, which you are not updating after the rotation.
So if we say that for portrait orientation your computation comes as 768/7 = ~109. However for Landscape orientation this calculation is not correct and constraint.constant value should be updated with 1024/7 = ~146. But you are not doing that, so an old value is being taken.
To resolve this, you can update each constraint every time device makes a rotation. But that is going to be tedious as you need to have a variable for each constraint and then iterate through all of them.
A better approach is to keep the equal width constraint to each UILabel with each other.
EqualWidth for each lable
label1.leading = segmentView.leading
label7.trainling = segmentView.trailing
label(n).trailing = label(n+1).leading
For reference check my answer here. Through it is applied in UIScrollView but it will help you understand this logic. And i have used VFL for that which is lot more concise.

Embedded a view and resize it

i'm having a problem with embedded views and auto layout.
I've created a view, which is a little complex. So now
I want to refactoring this view and create some view components. I got one of the views and take together in one uiview class, and put all its logic there. Lets call this view as XView. All right until now.
So I tried to embed XView in the main view, to see the view works, with its new component. I put this commands:
xViewInstance = ...
[self.container addSubview:xViewInstance];
It doesn't work. the xViewInstance is bigger than the parent view. I want to resize xViewInstance.
So I googled for answers to see what's going wrong. And I found some answers that could helped me. I found PureLayout.
So I tried with it.
- (void)updateViewConstraints {
if (!self.didSetupConstraints) {
[self.xViewInstance autoPinEdgesToSuperviewEdges];
self.didSetupConstraints = true;
}
[super updateViewConstraints];
}
It didn't work. xViewInstance continues bigger than its parent.
I found another answer here in stack, a code that create constraints in code, to adjusts subviews programmatically. Again it didn't work.
Now I have no ideia whats could be. I'm thinking that could some priority of the xViewInstance constraints.
Have someone ever passed for this situation? I would be very grateful if anyone can give some advice about this.
I believe this post will solve your problem:
Use autolayout to set dynamic UIView to match container view
I tested it like this and it worked:
- (void)viewDidLoad {
[super viewDidLoad];
// Init with a huge frame to see if it resizes.
xView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 800)];
xView.translatesAutoresizingMaskIntoConstraints = NO;
xView.backgroundColor = [UIColor redColor];
[containerView addSubview:xView];
[self addConstraints];
}
- (void)addConstraints
{
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0]];
}
Wg

How do I programatically use AutoLayout to set a UIView to the same width as its SuperView?

I have a simple UIView that I want to make the same width as the containing View's width. I want to do this programatically.
I can add a constraint in the containing View that makes the SubView's Width equal to the width of container. The C# is because i am using Xamarin iOS but this AutoLayout question is not specific to that.
View.AddConstraint(NSLayoutConstraint.Create(subView,
NSLayoutAttribute.Width,
NSLayoutRelation.Equal,
this.View,
NSLayoutAttribute.Width,
1.0f, 0.0f));
However it feels more natural to control this from within the SubView as it view will always be full width. How would I do that?
When I try and create the constraint from within the SubView I use this.SuperView as the Relation but it does not work. It throws the following Exception
NSInternalInconsistencyException Reason: Unexpected use of internal
layout attribute.
I got the same NSInternalInconsistencyException when trying to add a constraint involving the superview to which I wasn't attached yet. So maybe make sure that you attach first to the superview.
As per your question about how to set UIView size similar to superView.
You can set constraints by using two different ways.
I’ve created view and added it subview to superView.
UIView *redView;
redView = [UIView new];
[redView setBackgroundColor:[UIColor redColor]];
[redView setAlpha:0.75f];
[redView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:redView];
[self.view setBackgroundColor:[UIColor blackColor]];
1.) By using visual format.
NSDictionary *dictViews = #{#"red":redView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[red]-0-|" options:0 metrics:0 views:dictViews]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[red]-0-|" options:0 metrics:0 views:dictViews]];
2.) By using layout attributes.
Here constraintWithItem:redView - is subview to which we want to set constraints and toItem:self.view - is out superview according to which we need to set constraints.
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:1.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0 constant:1.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:1.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:1.0]];
Hope this will be helpful to you. Happy Coding.

Constraints for subview not working in iOS 8, XCode 6 (6A313). Works iOS 7

Simply adding a subview UIView from a controller creating using instantiateViewControllerWithIdentifier to the current controllers view and adding constraints to maintain the size of the new subview to cover the entire area. The code below worked in iOS 7. iOS 8 sizes the subview to a small portion of the upper left corner. iOS 7 does what I expect, which is to size the subview across the entire size of the parent view. I'd attach an image, but don't have the rep for that. Setting translatesAutoresizingMaskIntoConstraints to YES fixes the issue, but then the view does not honor the constraints and resize when orientation changes or sizing changes.
spotCheckStoresViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"visitStoresViewController"];
spotCheckStoresViewController.mainViewController = self;
spotCheckStoresViewController.dataMode = kDataModeViews;
spotCheckStoresViewController.lastRow = [self getViewsLastRow];
spotCheckStoresViewController.view.tag = -200;
currentView = spotCheckStoresViewController.view;
[self addChildViewController:spotCheckStoresViewController];
[self.view insertSubview:currentView belowSubview:_menuViewController.view];
currentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
//[self.view updateConstraints];
//[self.view layoutIfNeeded];
I've tried setting the frame of the subview as well. Any ideas what might have changed in iOS 8 to alter the behavior of constraints used in this way?
In my case, the problem related to constraints labeled UIView-Encapsulated-Layout-Width and UIView-Encapsulated-Layout-Height. When I removed them, everything behaved as though my view was of zero size, with everything centered on the upper left corner of the screen. When I left them in, new constraints worked as expected. I also retained the constraints labeled _UILayoutSupportConstraint.
In my case, it was only happening on iOS 8.3. There was a conflict with existing constraints. A colleague found that I needed to first remove any existing constraints before adding the others.
[self.view removeConstraints:self.constraints];

Resources