I have a simple UIInputViewController subclass with only two overridden methods. I use this input view controller as inputAccessoryViewController on my UIViewController subclass which becomes first responder. I try to specify height of inputView by adding constraint as Apple documentation recommends.
Problem is that my constraint doesn't work and I get autolayout exception when my constraint is being added
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
...
(
"<NSLayoutConstraint:0x178aa1d0 V:[UIInputView:0x178a4ae0(0)]>",
"<NSLayoutConstraint:0x178b9520 V:[UIInputView:0x178a4ae0(500)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x178b9520 V:[UIInputView:0x178a4ae0(500)]>
Which I think means that system already added a zero height constraint to the input view (because it is created with zero height). Now they conflict and autolayout breaks my constraint to fix the issue.
When I try to use it as inputViewController of my view controller (just for test purposes), I get same exception but instead of zero height it is 216 px. It also breaks my constraint and the height remains default.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.inputView.translatesAutoresizingMaskIntoConstraints = NO;
self.inputView.backgroundColor = [UIColor redColor];
}
- (void)updateViewConstraints {
CGFloat _expandedHeight = 500;
NSLayoutConstraint *_heightConstraint =
[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:0.0
constant: _expandedHeight];
[self.inputView addConstraint: _heightConstraint];
[super updateViewConstraints];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.view setNeedsUpdateConstraints];
}
As a result, I am not able to change input accessory view height. Has anyone succeed in it? Obviously, Apple documentation provides no help...
Since iOS 9.0 this can be solved with inputView.allowsSelfSizing = YES;
I don't know if this is the issue, but the problem may come from the 0.0 multiplier you are setting on _heightConstraint. Try to change it to 1.0.
It would look like this:
NSLayoutConstraint *_heightConstraint =
[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant: _expandedHeight];
Hope this helps!
When you make a view the input accessory view of a UITextView, as soon as the text view becomes first responder, a height constraint is added automatically set to whatever was sent as the frame height. For example:
let inputView = UIInputView(frame: CGRectMake(0, 0, view.bounds.width, 200),
inputViewStyle: .Default)
someTextView.inputAccessoryView = inputView
someTextView.becomeFirstResponder()
assert((inputView.constraints().last as NSLayoutConstraint).constant == 200)
You can modify this constraint after the fact.
I did it in Swift.
hope this helps you.
override func viewDidAppear(animated: Bool) {
let heightConstraint = NSLayoutConstraint(
item:self.view,
attribute:NSLayoutAttribute.Height,
relatedBy:NSLayoutRelation.Equal,
toItem:nil,
attribute:NSLayoutAttribute.NotAnAttribute,
multiplier:0.0,
constant:100)
self.view.addConstraint(heightConstraint)
}
Related
I have two button with equal width constraints. I want to remove equal width constraint and add a new width constraint for one button. the other button constraint to zero.
This is what I've tried. But it's not working. The Equal width constraint is not removing
NSLayoutConstraint * constraint = [self
constraintWithIndientifer:#"MyButtonWidth" InView:self.view];
[self.view removeConstraint:constraint];
NSLayoutConstraint * newconstraint = [NSLayoutConstraint constraintWithItem:self.departureButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:self.view.frame.size.width];
newconstraint.identifier = #"MyButtonWidth";
[self.departureButton addConstraint:newconstraint];
[self.view layoutIfNeeded];
-(NSLayoutConstraint *)constraintWithIndientifer:(NSString *)identifer InView:(UIView *)view{
NSLayoutConstraint * constraintToFind = nil;
for (NSLayoutConstraint * constraint in view.constraints ) {
if([constraint.identifier isEqualToString:identifer]){
constraintToFind = constraint;
break;
}
}
return constraintToFind;
}
You don't need to do this,
you can add another constraint such as width constraint to >=1
after that, you should set "equal constraint" "priority to 1
after that, you should set "width constraint" priority to 2 or more(default is 1000)
after that, when you set constraint constant it works and equal constraint is not working because of priority:
self.yourButtonWidthConstraint.constant = yourWidth;
If you look at the documentation for NSLayoutConstraint, you'll find an isActive property:
You can activate or deactivate a constraint by changing this property. Note that only active constraints affect the calculated layout.
Activating or deactivating the constraint calls addConstraint: and removeConstraint: on the view that is the closest common ancestor of the items managed by this constraint. Use this property instead of calling addConstraint: or removeConstraint: directly.
So going by that I'm changing both the removeConstraint and addConstraint you have in your sample code.
aka just change your code to this:
NSLayoutConstraint * constraint = [self
constraintWithIndientifer:#"MyButtonWidth" InView:self.view];
constraint.active = NO; // CHANGE 1
NSLayoutConstraint * newconstraint = [NSLayoutConstraint constraintWithItem:self.departureButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:self.view.frame.size.width];
newconstraint.identifier = #"MyButtonWidth";
newConstraint.active = YES; // CHANGE 2
[self.view layoutIfNeeded];
NOTE: the NSLayoutConstraint class also has two class funcs: + activateConstraints: and + deactivateConstraints:, which both take an array of constraints. That's preferred when you're changing multiple constraints, but not needed here since its only 1.
Loop though the constraints of the parent view of these two buttons and delete the constraint where first item is the first button and second is the second button and layoutAttribute is width
for (NSLayoutConstraint*co in self.parentOfBtns.constraints)
{
if(co.firstItem==self.btn1&&co.secondItem==self.btn2&&co.firstAttribute==NSLayoutAttributeWidth&co.secondAttribute==NSLayoutAttributeWidth)
[self.parentOfBtns removeConstraint:co];
}
then add your new width constraint , and don't forget to call
[self.view layoutIfNeeded];
I have a custom UIView subclass called CustomRectangle. I instantiate this in a ViewController and create all of its constraints in a ViewController. My goal is to create all the constraints inside this UIView subclass programmatically. The problem is that I don't know how to set the constraints there because I don't have reference to any of the other views in the Storyboard.
For example, if I want my view CustomRectangle to be centered based on another view, I would create an #IBOutlet in the ViewController for the other view and then use that to center CustomRectangle. I don't know if this is possible to do in a UIView subclass.
I want to do this based on MVC (Model View Controller) architecture.
What is the best practice for this? Any ideas on how to do this?
you should use initWithFrame method of CustomRectangle to do this.
for emample in UIView's Subclass,
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame: frame];
if (self) {
[self setFrame:CGRectMake(0, 0, 50, 50)];
//add constraint here for example
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute: NSLayoutAttributeNotAnAttribute multiplier:1 constant:50]];
}
return self;
}
Convert in swift this is objective c code!!
Hope this will help :)
There are a few things you should do:
Remember to set translatesAutoresizingMaskIntoConstraints = NO for the constraints to work. However, if you think views autolayout way, inside your view you should use autolayout for your subviews, and the view that instantiates your view should add constraints to customRectangle (a.k.a widht, alignment, etc)
In the init method, you can just add constraints that only depend on your view. Other constraints should be added from outside.
For Example:
CustomRectangle *customRectangle = [[CustomRectangle *customRectangle alloc] init];
// just to be sure
customRectangle.translatesAutoresizingMaskIntoConstraints = NO;
// just as an example
[self.view addConstraints[NSLayoutConstraint constraintWithItem:customRectangle attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]];
Hope it helps!!
Here is my problem, I have a scroll view scrollExerciseIndex that I use only as a scrolling bar, in this scroll view I place a UIView indexesView and I want it to be always at the center of the scroll view. For this I use layout constraints :
UIView * indexesView = [[UIView alloc] initWithFrame: CGRectMake(xPosition, 0, dimension*numberIndexes, dimension)];
[self.scrollExerciseIndex addSubview:indexesView];
[self.scrollExerciseIndex setContentSize:CGSizeMake(dimension*numberIndexes, dimension)];
if (xPosition != 0) {
NSLayoutConstraint * xCenterConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.scrollExerciseIndex attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
[self.scrollExerciseIndex addConstraint:xCenterConstraint];
}
Here is the expected result :
Don't pay attention to all the element, just the bar at the bottom of the screen is my problem.
I have to create view programmatically because sometimes I will activate the constraints, sometimes not and I have to set the frame of the view dynamically. So for now I initialise the view indexesView like so :
UIView * indexesView = [[UIView alloc] initWithFrame: CGRectMake(xPosition, 0, dimension*numberIndexes, dimension)];
(I know, not very original)
I would like to know if there is a way to initialize the view programmatically but to say to auto-layout that it has no constraints on the position because right now if the screen turns in landscape mode there is a conflict as the scrollview's frame changes so the distance between the center of the scroll view (on which I set a constraint) and the position of the subview's frame (xPosition) is no longer the same.
As you can see, the view is no longer at the center of the scroll view and I have some constraints broken.
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x7bed6c50 UIView:0x7bed6ad0.centerX == UIScrollView:0x7e273200.centerX
Thanks for your help.
Ok, I found what I was looking for by reading a book about Audio-Layout.
My problem was that audio layout would create constraints behind my back automatically. When using AutoLayout a type of constraints is created from non-autoLayout specifications (The used to describe interface when auto layout didn't exist). So constraints are created using the initial frame of the view. The only thing I had to do was :
[indexesView setTranslatesAutoresizingMaskIntoConstraints:NO];
to disable this creation of constraints from the frame, and then recreate explicitly the constraints for width and height if needed (which wasn't the case for me, but I still made the test) like so :
`NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:widthValue];
NSLayoutConstraint * heightConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:heightValue];
[indexesView addConstraint: heightConstraint];
[indexesView addConstraint: widthConstraint];`
When adding constraints programmatically, don't forget to call : [indexesView setNeedsUpdateConstraints]; so the constraints are recalculated only when needed.
Last info that I read and can be useful in general, when adding a lot of constraints, the apple doc specifies that it is more efficient to use the method :
[myView addConstraints:(NSArray<NSLayoutConstraints *> *)] than to call addConstraint: for each constraint.
Hope it can be useful to someone.
I am desperately trying to stick one of my UILabels to the right edge of it's superview while the label's width is variable (it's a time so the thing is getting bigger and should be expanding to the left, this is done using sizeToFit inside of the label when text is set).
So far I have tried loads of things but closest I got with:
_elapsedTimeRightConstraint = [NSLayoutConstraint constraintWithItem:_elapsedTimeView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:-150];
While the label is initially set to 150px width. But when I modify the constant, it all goes to hell.
_elapsedTimeRightConstraint.constant = (_elapsedTimeView.frame.size.width * -1);
[self layoutIfNeeded];
So my question is, how do I align trailing edges of a view and it's superview (so it sticks to the right) when the width of the subview is constantly changing. I have been using FLKAutoLayout elsewhere in the project so if this can be done this framework easily than great, but basic autolayout solution would be amazing too!!!
First, make sure that translatesAutoresizingMaskIntoConstraints is set to NO, if you are creating the label programmatically.
The first constraint you need is "label.trailing = superview.trailing".
[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTrailing
multiplier:1.f
constant:0.f]
This will pin the right edge (on left-to-right languages) of the label on the right edge of the superview.
You will now need a constraint for the Y position.
In my test, I have vertically centred the label with the following constraint:
[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeCenterY
multiplier:1.f
constant:0.f]
Now comes the trick!
Every time you change the text on the label, you need to recalculate the frames with AutoLayout.
[superview setNeedsLayout];
[superview layoutIfNeeded];
AutoLayout will:
1) Ask the label of its new size (based on its text).
2) Adjust the size of the label.
3) Pin the trailing edge of the label to the trailing edge of the superview.
Further research
The issue with UILabel is that when you're using AutoLayout and you set text, its intrinsicContentSize changes, but it doesn't trigger a layout update.
A way to enforce this without subclassing UILabel is to use Objective-C runtime.
#interface UILabel (AutoLayout)
- (void)swz_setText:(NSString*)text;
#end
#implementation UILabel (AutoLayout)
+ (void)load
{
NSLog(#"Swizzling [UILabel setFont:]...");
Method oldMethod = class_getInstanceMethod(self, #selector(setText:));
Method newMethod = class_getInstanceMethod(self, #selector(swz_setText:));
method_exchangeImplementations(oldMethod, newMethod);
}
- (void)swz_setText:(NSString*)text
{
if (![text isEqualToString:self.text]) {
[self setNeedsLayout];
}
[self swz_setText:text]; //This now points to "setText:" - not a mistake!
}
#end
In this category, I'm "enhancing" setText: implementation by calling setNeedsLayout if the text changes.
Now you just need to invoke layoutIfNeeded on the superview to recalculate/realign the label frame.
Click here for the playground (Swift 2.0 - Xcode 7) where I've tested my code.
I hope this helps.
Hello here are some points to achieve what you want:
Set NSLayoutConstraintTrailing constant equal to 0 and the leading constraint NSLayoutAttributeLeading set it as greater than or equal the value you want.
Use NSLayoutConstraintTrailing and NSLayoutAttributeLeading instead of right and left to handle other languages
I hope this helps
[yourLabel sizeToFit];
CGRect frame = yourLabel.frame;
frame.x = parentView.frame.size.width - yourLabel.frame.size.width;
yourLabel.frame = frame;
It completely ignores the "best practice" of using the autolayout features, but if you just can't stand it anymore....that should work. ;-)
I am new to Auto layout constraints. I have 2 views(topView and paintView) on my main view, along with a button on the top right corner of the main view. On loading the view, the topView occupies the whole main view(excluding the button). On click of the button, I want the topView to occupy 70% of the main view and the paintView to occupy the rest(excluding the button).
I have set up the the X, Y and top constraints for the topView using storyboard. The paintView and the corresponding constraints have been set up programmatically.
The code I have now is this:
-(void)setupPaintView
{
UIView *pPaintView = [UIView new];
[pPaintView setBackgroundColor:[UIColor yellowColor]];
pPaintView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:pPaintView];
self.paintView = pPaintView;
[self addConstraintsToView];
//[self setTopViewFrame];
}
-(void)addConstraintsToView
{
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.paintView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.topView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.topView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.paintView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
NSLayoutConstraint *pHeightConstraintTopView = [NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0];
self.heightconstraintTopView = pHeightConstraintTopView;
[self.view addConstraint:pHeightConstraintTopView];
NSLayoutConstraint *pHeightConstraintPaintView = [NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.0
constant:0.0];
self.heightconstraintPaintView = pHeightConstraintPaintView;
[self.view addConstraint:pHeightConstraintPaintView];
}
On button click the following method gets called:
-(IBAction)detailBtnClick:(id)sender
{
if(self.heightconstraintPaintView.constant == 0)
{
self.heightconstraintTopView.constant = 0.7*self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.frame.size.height;
[self.view setNeedsUpdateConstraints];
}
else
{
self.heightconstraintTopView.constant = self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0;
[self.view setNeedsUpdateConstraints];
}
}
When the view loads, the topView acquires the main view's height, which is desired here. But when I click on the button, the topView remains at 100% i.e. it does not resize and neither does the paintView. I am modifying the constant property of the topView and the paintView constraints, but I am not sure that is the correct way to go about it. The constraint here is that the views have to be laid out using Autolayout constraints only. How can I get the views to resize at the click of the button?
Any help is welcome.
Thanks to timothykc and others, I have successfully navigated the problem stated above. But I am facing another issue now.When I change the orientation of the simulator to landscape, the paintView remains almost hidden. Following is the code (toggle is a boolean value that decides whether to stretch/shrink the views):
-(IBAction)detailBtnClick:(id)sender
{
if(self.toggle == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.bounds.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.bounds.frame.size.height;
//[self.view layoutIfNeeded];
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
//[self.view layoutIfNeeded];
}
self.toggle = !self.toggle;
}
The topViewHeightConstraint has been added as a property as indicated by timothykc. This is working properly for the portrait orientation, but is not working properly for landscape, as the height of the topView does not change as desired(70%), meaning that the ratios are not getting handled properly.
I'm going to provide a storyboard driven solution that should help you with other autolayout problems down the road.
My solution to your specific problem, you've got two views (1 and 2 in diagram below):
For view 1, pin the view to the left, top, and right of the superview. Then set a height constant. (e.g. 568, the full height of an iphone 5s)
For view 2, pin it to the left, bottom, and right of the superview. Then pin it to the bottom of view 1.
Open up the assistant editor view, and here's the key trick--turn the height constraint on view 1 into a nslayoutconstraint property on your VC. You do this by locating the constraint, and then control-dragging onto the VC. (e.g.`
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewHeight;`
Now you can manipulate this property with an action linked to your button, such as
- (IBAction)scale:(id)sender {
self.viewHeight.constant = 397.6; //%70 of 568....
}
In my example, I change the nslayoutconstraint.CONSTANT manually to an arbitrary value.
To understand what's happening, you need to know that autolayout is a means for determining the (x coord,y coord,width, height) of any layout object. Warnings occur when xcode cannot ascertain all 4 values...
In View 1, we give a constraint for Height. X,Y, and Width are extrapolated from the distance to the superview. (if something is 0 from the left and right, then the width fills the whole screen...; if 0 from top and left, then coords must be (0,0))
In view 2, X must be 0 since distance from left is 0. width whole screen... Height and Y are extrapolated based on the height of View 1!
So when we mess with height constraint in View 1, it effects the height and Y coord of View 2!
To get constraints to update on a view you would need to call [self.view layoutIfNeeded]; instead of [self.view setNeedsUpdateConstraints]; after setting the new constant on whichever constraint(s) you would like to update.
Actually this is more of an comment about my methods, but I decided to post it as an answer because firstly, this has solved my problem and secondly, it involves some snippets of code which is hard to read in the comments section. Regarding the orientation problem mentioned in the edit, I came up with a workaround to accommodate the view reszing requirements with respect to the toggle button and with respect to orientation change. The three methods used for this purpose are:
The following method is called on the button click event.
-(IBAction)detailBtnClick:(id)sender
{
[self updateViewConstraints:self.toggle];
self.toggle = !self.toggle;
}
The following method updates the constraints.
-(void)updateViewConstraints :(BOOL)toggleValue
{
if(toggleValue == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.bounds.size.height;
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
}
}
The following method calls the method above to update constraints in case of orientation change:
-(void)viewWillLayoutSubviews
{
[self updateViewConstraints:!self.toggle];
}