NSLayoutConstraint for view on UITableView - autolayout

I have a UIViewController with a UITableView as well as a UIView that is above the UITableView but aligned to the bottom of the screen. Problem is when the user is on a phone call, the frame settings don't work, and the UIView is pushed below the screen (so it is slightly covered. I'm trying to just align the UIView to the view bottom and simply not getting it to align. I have the constraints defined as below:
[self.view addSubview:self.signupView];
[self.view addSubview:self.tableView];
[self.view bringSubviewToFront:self.signupView];
NSArray *v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_signupView(70)]-0-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:NSDictionaryOfVariableBindings(_signupView)];
NSArray *h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_signupView]|"
options:NSLayoutFormatAlignAllBottom
metrics:nil
views:NSDictionaryOfVariableBindings(_signupView)];
[self.view addConstraints:h];
[self.view addConstraints:v];
The height of signupView is 70, and it should just be aligned at the bottom of the screen with the width of the entire screen. The above code is putting the view at the top of the view. Is there something i'm missing here? Why is it not aligning with the bottom as I put above? I tried to read the below on this:
http://commandshift.co.uk/blog/2013/01/31/visual-format-language-for-autolayout/
http://makeapppie.com/2014/07/26/the-swift-swift-tutorial-how-to-use-uiviews-with-auto-layout-programmatically/
Use autolayout to set dynamic UIView to match container view

Figured it out.
Here is the below that worked for me:
NSArray *v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[_signupView(70)]-0-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:NSDictionaryOfVariableBindings(_signupView)];
NSArray *h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_signupView]|"
options:NSLayoutFormatAlignAllBottom
metrics:nil
views:NSDictionaryOfVariableBindings(_signupView)];
[self.view addConstraints:h];
[self.view addConstraints:v];

Related

Xcode6 - Autolayout view in an other autolayout view

This problem didn't exist on xCode 5 even with ios8.
The green square have to be in the red square cause the greenView is a subview of the red view. But built with xCode6 the position of the greenView is not relative to its parent.
- (void)viewDidLoad {
[super viewDidLoad];
/**** 1 - REDVIEW, THE CONTAINER *****/
UIView *redView = [UIView new];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
UIView *spaceView = [UIView new];
spaceView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:spaceView];
NSDictionary *views = #{#"spaceView" : spaceView,
#"redView": redView};
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[spaceView]|"
options: 0
metrics:0
views:views]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[spaceView][redView]|"
options: NSLayoutFormatAlignAllRight | NSLayoutFormatAlignAllLeft
metrics:0
views:views]];
//CENTER VERTICALY
NSLayoutConstraint *constraint = [NSLayoutConstraint
constraintWithItem:redView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:spaceView
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0];
[self.view addConstraint:constraint];
/**** 1 - GREENBUTTON, IN THE CONTAINER *****/
UIButton *greenButton = [[UIButton alloc] init];
greenButton.backgroundColor = [UIColor greenColor];
greenButton.translatesAutoresizingMaskIntoConstraints = NO;
[greenButton addTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
[redView addSubview:greenButton];
views = #{#"button" : greenButton};
[redView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[button(35)]|"
options: 0
metrics:0
views:views]];
}
Where is the trouble?
(you can copy and paste those line to try)
The constraints defined are positioning the greenButton relative to its superview. The issue lies in the missing the vertical position constraint. Also note that the visual syntax you used resulted in conflicting constraints:
0 distance to leading
0 distance to trailing (I removed this one to make it work)
width of 35
superview of witdth != 35;
If you use the following constraints for the greenButton, it will be placed top-left on its parent.
[redView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[button(35)]"
options: 0
metrics:0
views:views]];
[redView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[button(35)]"
options:0
metrics:0
views:views]];

UIView not resizing when rotated with a CGAffineTransform under iOS8

I have a UIViewController that only rotates some of it subviews when the device is rotated. This works fine under iOS7 but breaks under iOS8. It appears that the UIView's bounds are adjusted by the transform under iOS8. This was unexpected.
Here's some code:
#interface VVViewController ()
#property (weak, nonatomic) IBOutlet UIView *pinnedControls;
#property (nonatomic, strong) NSMutableArray *pinnedViews;
#end
#implementation VVViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.pinnedViews = [NSMutableArray array];
[self.pinnedViews addObject:self.pinnedControls];
}
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[UIViewController rotatePinnedViews:self.pinnedViews forOrientation:self.interfaceOrientation];
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation) && UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
[UIViewController rotatePinnedViews:self.pinnedViews forOrientation:toInterfaceOrientation];
}
}
#end
We've made a category on UIViewController to handle this behavior. Here's the pertinent code:
#implementation UIViewController (VVSupport)
+ (void)rotatePinnedViews:(NSArray *)views forOrientation:(UIInterfaceOrientation)orientation {
const CGAffineTransform t1 = [UIViewController pinnedViewTansformForOrientation:orientation counter:YES];
const CGAffineTransform t2 = [UIViewController pinnedViewTansformForOrientation:orientation counter:NO];
[views enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
// Rotate the view controller
view.transform = t1;
[view.subviews enumerateObjectsUsingBlock:^(UIView *counterView, NSUInteger idx, BOOL *stop) {
// Counter-rotate the controlsUIin the view controller
counterView.transform = t2;
}];
}];
}
+ (CGAffineTransform)pinnedViewTansformForOrientation:(UIInterfaceOrientation)orientation counter:(BOOL)counter {
CGAffineTransform t;
switch ( orientation ) {
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationPortraitUpsideDown:
t = CGAffineTransformIdentity;
break;
case UIInterfaceOrientationLandscapeLeft:
t = CGAffineTransformMakeRotation(counter ? M_PI_2 : -M_PI_2);
break;
case UIInterfaceOrientationLandscapeRight:
t = CGAffineTransformMakeRotation(counter ? -M_PI_2 : M_PI_2);
break;
}
return t;
}
#end
Here's what the nib looks like:
The UIView named pinned in the nib is the IBOutlet of pinnedControls:
When I run this in portrait mode under iOS7 or iOS8 I get this:
And I see the desired outcome under iOS7 in landscape mode:
But under iOS8 (GM) I do not get this behavior. This is what I see instead:
Notice that the center of the UILabel with the text "Pinned Label" is maintaining its distance from the bottom of the pinned UIView, which has not changed size to accommodate the rotation. That UIView has all its edges pinned to the top, left, bottom and right sides of the super view.
It looks to me that the transform property interacts with Auto Layout differently under iOS8. I'm a bit baffled here. I know I can't rely on the frame. I may just start manually setting bounds but that just seems like the wrong thing to do, essentially do an end run around Auto Layout.
So this was driving me crazy for the past couple days and I was able to fix by changing the timing of the setTransform call in my animations block
When going to landscape, I'm setting the transform AFTER setting up the new frame. When going portrait, I'm setting the transform BEFORE setting up the new frame. All this was going inside the animations block on the "animateWithDuration..." method
I'm not sure if it will help you directly with your code, but it might spark some inspiration to solve it since we are definitely having a similar issue
This is more of a work around than a fix so it may not help everybody in similar situations. My problem was that the outer "pinned" view was being resized again after the transform was applied.
My solution was to change the constraints on the pinned view to be center vertically, center horizontally, and width and height equal a constant.
Then, in viewDidLoad, I set the height and width of the pinned view's frame to be the height of the main screen. This makes the view square so I don't care if it gets an extra rotate.
in ios8, uiviewcontrollers need to be resized with uitraitcollections depending on the device orientation. Otherwise, you get a uiview in portrait mode, while the phone oriented in landscape, when you try to rotate it. So the correct steps are to rotate AND override uitraitcollections
EDIT:
I override my uitraitcollection with the following code
UITraitCollection *horTrait = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
UITraitCollection *verTrait = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
UITraitCollection *finalTrait = [UITraitCollection traitCollectionWithTraitsFromCollections:#[horTrait,verTrait]];
[self.parentViewController setOverrideTraitCollection:finalTrait forChildViewController:self];
unfortunately, it doesnt work if the uiviewcontroller im trying to modify does NOT have a parentviewcontroller :'(

How align 2 uibuttons programmatically in a layout?

I use the KLCPopup library to display a popup in my app.
I have to add 2 buttons horizontally on the bottom of this popup, like the standard iOS UIAlerView. I cannot align this two buttons horizontally.
Here is the result I want:
But here is the result I have:
Here is the code I use to create this popup:
UIView* contentView = [[UIView alloc] init];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
UILabel* dismissLabel = [[UILabel alloc] init];
dismissLabel.translatesAutoresizingMaskIntoConstraints = NO;
dismissLabel.numberOfLines = 0;
[dismissLabel setTextAlignment:NSTextAlignmentCenter];
dismissLabel.lineBreakMode = NSLineBreakByWordWrapping;
dismissLabel.preferredMaxLayoutWidth = 200;
UIButton* dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
dismissButton.translatesAutoresizingMaskIntoConstraints = NO;
dismissButton.contentEdgeInsets = UIEdgeInsetsMake(10, 20, 10, 20);
[contentView addSubview:dismissLabel];
[contentView addSubview:dismissButton];
UIButton* cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
cancelButton.contentEdgeInsets = UIEdgeInsetsMake(10, 20, 10, 20);
[contentView addSubview:cancelButton];
NSDictionary* views = NSDictionaryOfVariableBindings(contentView, dismissButton, cancelButton, dismissLabel);
[contentView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(16)-[dismissLabel]-(16)-[dismissButton]-(16)-[cancelButton]-(16)-|"
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:views]];
[contentView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(18)-[dismissLabel]-(18)-|"
options:0
metrics:nil
views:views]];
// Show in popup
KLCPopupLayout layout = KLCPopupLayoutMake(KLCPopupHorizontalLayoutCenter,
KLCPopupVerticalLayoutCenter);
KLCPopup* popup = [KLCPopup popupWithContentView:contentView
showType:KLCPopupShowTypeFadeIn
dismissType:KLCPopupDismissTypeGrowOut
maskType:KLCPopupMaskTypeDimmed
dismissOnBackgroundTouch:NO
dismissOnContentTouch:NO];
[popup showWithLayout:layout];
I think the problem on my code is the constraint.I've tried many possibilities but I cannot find the right way to do this.
Any advice will be helpful!
Thanks
You've added all three views to the vertical constraint and that's why they are all one after another. Do something like this:
V:|-16-[dismissLabel]
V:|-80-[dismissButton]
V:|-80-[cancelButton]
H:|-18-[dismissLabel]-18-|
H:[dismissButton(50)]-100-[cancelButton(50)]
I haven't tested it but something like that could work. You might have to modify the last H value or maybe add a centering option to it. There are probably better ways where you could group some of those things but this should work too.

using nsautolayout make a group of uiview in the center of their superview

I would like to make numbers of uiview in the center of the red area.
How could I do that with NSAutolayout ? Even when users rotate?
landscape mode
top
....................................
- view - ... 200px margin from right
| gap
- view -
| gap
- view -
| gap
- view -
....................................
bottom
Your question isn't really clear.
You can create constraints to center a view with respect to another view in a given orientation. For example, you can set one view's "centerX" attribute to equal some other view's centerX. (You could also make it equal that other view's leading, trailing, left, or right edge. Or even other attributes that don't make much intuitive sense, like width, top, etc., if that's what you want.)
If you're trying to vertically center a group like the stack of views in your diagram, there are a couple of approaches. First, you can embed the group into another view that closely encloses it. Its top would equal the top of the first view in the stack and its bottom would equal the bottom of the last view in the stack. Then you could set up a constraint to keep that container view centered within its superview.
The other approach is to create hidden spacer views. Put one view, the top spacer, above the first view in your stack. Make its top equal the top of the superview. Make its bottom equal the top of the first view in your stack. Similarly, set up a spacer at the bottom. Its top would equal the bottom of the last view in your stack. Its bottom would equal the bottom of the superview. Then, set a constraint between the top spacer and the bottom spacer to make their heights equal. That ensures that the space between the stack and the superview is the same on the top and the bottom.
The same techniques work for the horizontal direction, too, if necessary.
Update:
Here's some code which I haven't tested at all:
UIView* redArea = /* ... */;
NSArray* stackViews = /* ... */;
UIView* container = [[UIView alloc] initWithFrame:NSZeroRect];
[redArea addSubview:container];
UIView* previousView = nil;
for (UIView* view in stackViews)
{
[container addSubview:view];
if (previousView)
{
// Make a gap between the stacked views
NSDictionary* views = NSDictionaryOfVariableBindings(view, previousView);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[previousView]-[view]" options:0 metrics:nil views:views];
[container addConstaints:constraints];
}
else
{
// Make the top of the container the same as the top of the first stacked view
NSDictionary* views = NSDictionaryOfVariableBindings(view);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]" options:0 metrics:nil views:views];
[container addConstaints:constraints];
}
// Make sure the container is at least as wide as each stacked view
NSDictionary* views = NSDictionaryOfVariableBindings(view);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[view]-(>=0)-|" options:0 metrics:nil views:views];
[container addConstaints:constraints];
previousView = view;
}
if (previousView)
{
// Make the bottom of the container the same as the bottom of the last stacked view
NSDictionary* views = NSDictionaryOfVariableBindings(previousView);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[previousView]|" options:0 metrics:nil views:views];
[container addConstaints:constraints];
}
// Make the container as narrow as possible after satisfying other constraints
NSLayoutConstraint* minimalWidth = [NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0];
minimalWidth.priority = UILayoutPriorityFittingSizeLevel - 1;
[container addConstraint:minimalWidth];
// Center the container within the red area
NSLayoutConstraint* centerVertically = [NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:redArea attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint* centerHorizontally = [NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:redArea attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
[redArea addConstraints:#[centerVertically, centerHorizontally]];
This would all be easier to set up in IB.

UINavigationItem multiple line prompt text

Can any body give me the solution for displaying UINavigationItem prompt text in 2 lines?
There is not a built-in way to do this. Below is a work-around that seems to work pretty well that I put together from stackOverflow post UINavigationItem with prompt and activity indicator
Here is a simulator screen shot of what it creates:
Note that since the text is a UILabel you can modify its color, font, or anything else too.
// I have this code in viewDidLoad
UIView *viewContainingPrompt;
UIBarButtonItem *promptButtonItem;
// Configuring the prompt title of the navigation bar so it is present but empty
[self.navigationItem setPrompt: #""];
// We will create a UIBarButtonItem that has a custom view (viewContainingPrompt).
// A subview of viewContainingPrompt will be a UILabel (headerLabel)
// We need to have this "intermediate" view to position the label at the right position
// (the UIBarButtonItem ignores the origin and height of its custom view)
viewContainingPrompt = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 0, 85)];
viewContainingPrompt.autoresizingMask = UIViewAutoresizingFlexibleWidth;
// Choose a width that puts 10 points on either end...
CGFloat labelWidth = self.navigationController.navigationBar.bounds.size.width - 20.0;
// Note that the '-60' below is determined by the width of the back button
// If someone can figure out how to determine this width at runtime this code
// would be much more robust.
UILabel *headerLabel = [[UILabel alloc] initWithFrame: CGRectMake(-60,-8,labelWidth,36)];
headerLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
headerLabel.text = #"A quite long prompt string that will wrap to a second line to demonstrate multiline prompt.";
headerLabel.font = [UIFont systemFontOfSize: 14];
headerLabel.numberOfLines = 0; // Zero gives as many lines as will fit, could be 2
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.textColor = [UIColor colorWithRed: .1 green: .1 blue: .2 alpha: 0.8f];
headerLabel.shadowColor = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.5f];
headerLabel.shadowOffset = CGSizeMake( 0, 1 );
headerLabel.textAlignment = UITextAlignmentCenter;
[viewContainingPrompt addSubview: headerLabel];
//[headerLabel release]; // Uncomment if not using ARC
promptButtonItem = [[UIBarButtonItem alloc] initWithCustomView: viewContainingPrompt];
self.navigationItem.leftBarButtonItem = promptButtonItem;
self.navigationItem.leftItemsSupplementBackButton = YES;
//[viewContainingPrompt release]; // Uncomment if not using ARC
//[promptButtonItem release]; // Uncomment if not using ARC
I would appreciate anyone's feedback on how to figure out the width of the back button during execution so that width did not have to be hard coded.
As it is I do not think there are any private APIs or other illegal code contained.

Resources