UIView programatic constraint height increase when keyboard present - ios

I'm building a comment input control for an app. This control consists of a UITextView embedded in a UIView. All constraints are being handled programatically. What happens is when the user taps the UITextView, the keyboard will open. This calls the keyboard observer methods and I then adjust the bottom constraint for the comment input control to move up with the keyboard. However, I am also trying to increase the height of the input control at the same time so the user has more room to type. I'm having trouble achieving this.
-(void)updateViewConstraints
{
NSDictionary *views = #{
#"table" : self.commentsTableView,
#"seeMoreComments" : self.seeMoreCommentsView,
#"commentInput" : self.commentEntryInput
};
//See More Comments Constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[seeMoreComments]-0-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[seeMoreComments(45)]" options:0 metrics:nil views:views]];
//Table view constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[table]-0-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[seeMoreComments]-0-[table]-0-|" options:0 metrics:nil views:views]];
//Comment entry input
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[commentInput]-0-|" options:0 metrics:nil views:views]];
commentInputVerticalConstraint = [NSLayoutConstraint constraintWithItem:self.commentEntryInput
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:commentInputHeight];
if(commentInputBottomConstraint == nil)
{
commentInputBottomConstraint =
[NSLayoutConstraint constraintWithItem:self.commentEntryInput
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom multiplier:1.0
constant:0.0];
}
[self.view addConstraint:commentInputVerticalConstraint];
[self.view addConstraint:commentInputBottomConstraint];
[super updateViewConstraints];
}
Now I have a method that is called when keyBoardWillShow is called. This method animates the comment input control up when the keyboard appears.
(void)animateContentWithKeyboardInfo:(NSDictionary *)keyboardInfo
{
NSNumber *animationDuration = keyboardInfo[ UIKeyboardAnimationDurationUserInfoKey ];
NSValue *keyboardFrameValue = keyboardInfo[ UIKeyboardFrameEndUserInfoKey ];
CGRect keyboardFrame = [keyboardFrameValue CGRectValue];
UIViewAnimationCurve animationCurve = [keyboardInfo[ UIKeyboardAnimationCurveUserInfoKey ] intValue];
UIViewAnimationOptions animationOptions = animationOptionWithCurve(animationCurve);
commentInputBottomConstraint.constant = (keyboardFrame.origin.y - [UIScreen mainScreen].bounds.size.height);
//Increase the veritcal height of the comment input control
commentInputVerticalConstraint.constant = 125;
//Takes into account that the Tab Bar is 50 points, and adjust for this
//value.
if(keyboardAppeared == YES)
{
commentInputBottomConstraint.constant += TAB_BAR_OFFSET;
}
else
{
commentInputBottomConstraint.constant -= TAB_BAR_OFFSET;
}
[self.view layoutIfNeeded];
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:[animationDuration floatValue] delay:0.0 options:animationOptions animations:
^{
[self.view layoutIfNeeded];
} completion:nil];
}
However, when I try to adjust the constant of the of the commentInputVerticalConstraint I receive this error message:
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the `UIView` property `translatesAutoresizingMaskIntoConstraints`)
(
"<NSLayoutConstraint:0x1899fcb0 V:[CommentEntryInput:0x176e5160(50)]>",
"<NSLayoutConstraint:0x1899e5c0 V:[CommentEntryInput:0x176e5160(125)]>"
)
I'm not sure if there is a way for me to "reset" or adjust the constraint to handle when the keyboard appears and then put it back to normal when the keyboard disappears. Any help would be appreciated.

Your problem is that -(void)updateViewConstraints is getting called more than once. So you are creating a new constraint and adding it to the view twice. Try checking if the constraint is nil or not.
I also don't think you need the first [self.view layoutIfNeeded] before the animation change of the constant. When changing constant, just set it then wrap the [self.view layoutIfNeeded] in a animation block to animate to that new value.

Related

Changing programmatic/VFL constraints on-the-fly

I'm manually adding a subview to a view and am positioning it with constraints...
UIView *superview = self.view;
UIView *subview = self.subview;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(superview, subview);
[self.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
[self.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
This positions my subview neatly in the superview. However, at a later time I would like to apply a margin around my subview (with animation) so that it is inset by 100. So in effect, my constraints in visual format language would be...
"H:|-(100)-[subview]-(100)-|"
"V:|-(100)-[subview]-(100)-|"
How can I attach variables to my 'margin' value so that I can transition between the two types of display for the subview?
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]
Will return an array with the constraints in the same order that they were specified, you can look for it inside the array and then change its constant value.
But I think the easiest way is to create a reference to it like so (you can store it in an internal variable or property):
NSLayoutConstraint* space = [NSLayoutConstraint constraintWithItem:self.button1 attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual toItem:self.button2
attribute:NSLayoutAttributeLeft multiplier:1.0 constant:12.0];
and add it like this:
[self.view addConstraints:#[space]];
then you can change space.constant to some value. and animate it like so:
[UIView animateWithDuration:1.0 animations:^{
// Make all constraint changes here
[self.view layoutIfNeeded];}];
Other approach would be to remove all constraints and add VFL constraints with updated values, and then perform layoutIfNeeded inside the animation block as above.

how to set adaptive layout at run time?

how to set adaptive layout at run time?
I am developing iOS app using adaptive layout with multiple views. I want to change the views position when keyboard will pop up.
I am using below auto layout coding it was not working.
NSArray *constraint_POS_V = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-vSpacing-[redView]-vSpacing-|" options:NSLayoutFormatDirectionLeadingToTrailing
metrics:metrics
views:viewsDictionary];
NSArray *constraint_POS_H = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-hSpacing-[redView]-hSpacing-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:metrics
views:viewsDictionary];
[self.view addConstraints:constraint_POS_V];
[self.view addConstraints:constraint_POS_H];
You have to set the constant value for the constraint at runtime. Take outlet for top layout constraint (viewTopLayoutConstraint) for the view.
- (void) keyboardWillShow:(NSNotification *) notification
{
[UIView animateWithDuration:0.3 animations:^
{
_viewTopLayoutConstraint.constant = -100;
[self.view layoutIfNeeded];
}];
}

Work with Autolayout programmatically

i try to achieve the following Design with UIView´s, it should be all done with Autolayout and no hard coded Frames:
I do it with an "for loop" like this:
CGFloat y_coordinate = 0;
for(int i = 0;i < 3;i++)
{
UIView * testView = [UIView new];
testView.frame = CGRectMake(testView.frame.origin.x, y_coordinate, testView.frame.size.width, testView.frame.size.height);
testView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = #{#"testView":testView};
testView.backgroundColor = [UIColor redColor];
[self.view addSubview:testView];
//View should have the same Width and Height and a little Padding between them!
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:testView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:-4]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:testView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:testView
attribute:NSLayoutAttributeHeight
multiplier:1
constant:-4]];
if(i % 2) {
//odd
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[testView]-0-|"
options:0
metrics:nil
views:viewsDictionary]];
//Here i need the Height of the View and not hard coded like here...but how?
y_coordinate += 200;
} else {
//even
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[testView]"
options:0
metrics:nil
views:viewsDictionary]];
}
}
My Problem is that i don´t know how to place the Views correctly under each other with Autolayout?
My Suggestion to you will be go through the auto layout tutorials online, and have a clear understanding of how the views layout themselves under "Autolayout".
One such example would be Ray Wenderlich's tutorial. Although this tutorial has not been dedicated for doing autolayout programmatically, but it does give a clear understanding of how the views are placed with the help of autolayout feature.
http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1
#deathhorse answer may work but the metrics argument is designed for this...
NSDictionary *metrics = #{#"margin": #(y_coordinate)};
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(margin)-[testView]"
options:0
metrics:metrics
views:NSDictionaryOfVariableBindings( testView )]];
This makes it more readable too.
EDIT
Having looked at your question again I think you are going about this the wrong way anyway.
Yes, you can use Auto Layout inside the views to lay out the labels and image and stuff....
However, this view should be using a UICollectionView and not just laid out in a for loop like this. Even if you're only adding 4 and you don't want it to scroll it would be much more flexible as a UICollectionView.
that's quite simple, create your constraint like this
[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:|-(%.0f)-[testView]", y_coordinate]
options:0
metrics:nil
views:NSDictionaryOfVariableBindings( testView )]];

Resize subviews in Autolayout visual form

I've failed on migrating my app from Autoresize to Autolayout mechanism. All I need is a view with dynamic size and subviews that will be connected to hte left top corner with the same size as their superview. In IB I've set size for PieView (My superview) as 186x186. And let the IB to generate all needed Constraints (I will create new ones in code):
PieView.m -updateConstraints()
- (void)updateConstraints {
[super updateConstraints];
[self removeConstraintsAffectingViewAndSubviews];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
CGFloat sizeCoef = 0.7f;
CGFloat percent = [self.category getFillinPercent] / 100.0f;
percent = percent > 1.0f ? 1.0f : percent;
sizeCoef += (1.0f - sizeCoef) * percent;
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:100.f];//kDiameter * sizeCoef];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:100.0f];//kDiameter * sizeCoef];
//_imgEmptyCircle, _imgFullCircle - UIImageViews
NSDictionary *views = NSDictionaryOfVariableBindings(_imgEmptyCircle, _imgFullCircle, self);
NSDictionary *metrics = #{#"height":#100.0};
[self addConstraints:#[width, height]];
NSString *visualForm = #"H:|[_imgEmptyCircle(height)][_imgFullCircle(height)]|";
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:visualForm
options:0
metrics:metrics
views:views];
visualForm = #"V:|[_imgEmptyCircle(height)][_imgFullCircle(height)]|";
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:visualForm
options:0
metrics:metrics
views:views];
[self addConstraints:verticalConstraints];
[self addConstraints:horizontalConstraints];
}
The result really surprised me. Blue one displayed correctly as I want, but green one has strange behavior.
Off course I have error: Unable to simultaneously satisfy constraints.
First imageView:
NSIBPrototypingLayoutConstraint:0xa2795d0 'IB auto generated at build time for view with fixed frame' V:[UIImageView:0x8a37150(186)]>
NSLayoutConstraint:0xa0305c0 V:[UIImageView:0x8a37150(100)]
Second imageView: the same one
Probable for PieView:
(
NSLayoutConstraint:0x8a40750 V:[CEPieView:0x8a36e50(100)],
NSLayoutConstraint:0xa030570 V:|-(0)-[UIImageView:0x8a37150] (Names: '|':CEPieView:0x8a36e50 ),
NSLayoutConstraint:0xa0305c0 V:[UIImageView:0x8a37150(100)],
NSLayoutConstraint:0xa0305f0 V:[UIImageView:0x8a37150]-(0)-[UIImageView:0xa277cf0]>
NSLayoutConstraint:0xa030620 V:[UIImageView:0xa277cf0(100)],
NSLayoutConstraint:0xa030650 V:[UIImageView:0xa277cf0]-(0)-| (Names: '|':CEPieView:0x8a36e50 )
)
Please, give me an advise how to fix it. How to create and old autoresizing mask(UIViewAutoresizingFlexibleHeight, UIViewAutoresizingFlexibleWidth) with the help of AutoLayout mechanism. Any help will be appreciated.
The way you're setting the constraints is wrong. Basically you're asking the superview to be 100pts by 100pts, and to contains two subviews that are each 100pts by 100pts and are beside AND on top of each other. So first your superview would have to be 200pts wide and/or tall, and then you can't have two view that are both beside each other and on top.
What you really want to do (I think) is having:
NSString *visualForm = #"H:|[_imgFullCircle]|"; // No need to explicitly set the height
Then add the corresponding constraint, then:
visualForm = #"H:|[_imgEmptyCircle(height)]|";
And add another constraint for this, and do the same for vertical constraints.
Basically you'd end up with:
//_imgEmptyCircle, _imgFullCircle - UIImageViews
NSDictionary *views = NSDictionaryOfVariableBindings(_imgEmptyCircle, _imgFullCircle, self);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imgEmptyCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imgFullCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imgEmptyCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imgFullCircle]|"
options:0
metrics:nil
views:views]];

UIView Awful Performance

I have built a slide navigation controller which allows the sliding of UIViewController's in a fast and smooth way (Facebook style). I have since added a UIViewController with a child view controller with it's own child view controller, as well as a table view. The lowest child view controller also has a view subviews. This UIViewController has the view that is made visible when the top view is slid away.
I have tested the smoothness both with and without this view controller. Without it the slide runs at a nice 60 frames per second, but with it my device is only managing a choppy 20 to 30 fps.
I have also tried removing subviews which seems to make a slight improvement, but not as much as I would expect. Even when switched out with another fairly complex view controller (without child view controls though) I get much better performance.
I am using auto layout throughout the app, and have come to believe this is the problem. None of my code is being executed whilst this poor sliding is occurring, so I believe it has something to do with the constraints being calculated as more of the view is being shown, but can't be sure.
As to avoid including the whole class, here are the updateViewConstraint methods in the view controller and subviews:
TOP VIEW CONTROLLER
/**
* Called when the view controller’s view needs to update its constraints.
*/
- (void)updateViewConstraints
{
[super updateViewConstraints];
// remove all constraints
[self.view removeConstraints:self.view.constraints];
NSArray *constraints;
// add the table view to the top of the view and the parameters view to the bottom
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(Panel)-[tableView]|"
options:kNilOptions
metrics:#{#"Panel": #(kPanelWidth)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tableView]-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(PanelPlus)-[recipeParameters]-|"
options:kNilOptions
metrics:#{#"PanelPlus": #(kPanelWidth + 20)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[recipeParameters(==Height)]|"
options:kNilOptions
metrics:#{ #"Height" : #(kParametersControllerHeight)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
FIRST CHILD VIEW CONTROLLER
/**
* Called when the view controller’s view needs to update its constraints.
*/
- (void)updateViewConstraints
{
[super updateViewConstraints];
// remove all constraints
[self.view removeConstraints:self.view.constraints];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[pageView]|" options:kNilOptions
metrics:nil views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[pageView]|" options:kNilOptions
metrics:nil views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
SECOND CHILD VIEW CONTROLLER
- (void)updateViewConstraints
{
[super updateViewConstraints];
NSArray *constraints;
// remove all constraints
[self.view removeConstraints:self.view.constraints];
if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(8)-[optionLabel]-[selectionWheel]-[includeExclude(==32)]-|"
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[selectionWheel]-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[includeExclude]-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
else
{
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[selectionWheel]-|" options:kNilOptions
metrics:nil views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[optionLabel]-[selectionWheel]-(Balance)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:#{#"Balance": #(self.optionLabel.bounds.size.width + 40)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
}
CUSTOM UICONTROL (SUBVIEW OF SECOND CHILD VIEW CONTROLLER)
/**
* Update constraints for the view.
*/
- (void)updateConstraints
{
[super updateConstraints];
[self removeConstraints:self.constraints];
[self.excludeButton removeConstraints:self.excludeButton.constraints];
[self.includeButton removeConstraints:self.includeButton.constraints];
NSArray *constraints;
NSLayoutConstraint *constraint;
// add everything to fill the bounds
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(==6)-[excludeButton]-(==6)-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[excludeButton][optionLabel][includeButton]|"
options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:self.viewsDictionary];
[self addConstraints:constraints];
// make the exclude and include buttons square
constraint = [NSLayoutConstraint constraintWithItem:self.excludeButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.excludeButton attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f];
[self.excludeButton addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.includeButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.includeButton attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f];
[self.includeButton addConstraint:constraint];
}
The implementation of the top UIViewController is here.

Resources