Auto Layout implementation in iOS using VFL - ios

I am using the auto layout using the visual formatting language.
In horizontal mode, I can include both labels in a single line of code like this
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-20-[Btn1]-10-[dummyLabel1]-10-[Lbl2]-10-[dummyLabel2]-10-[Btn2]" options:0 metrics:metrics views:views];
[self.view addConstraints:constraints];
I had to use to two lines of constraints like this to place two objects in vertical mode.
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[dummyLabel]-16-[fixedLabel]-13-|" options:0 metrics:metrics views:views];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[dummyLabel2]-16-[fixedLabel]-13-|" options:0 metrics:metrics views:views];
[self.view addConstraints:constraints];
Is there any way i can do this in a single line for the vertical mode as well using VFL?

No. There's a lot of different ways to do Auto Layout: in Interface Builder, using the VFL, creating NSLayoutConstraints programmatically, or using Layout Anchors. Each has their own shortcomings. This is one of the places the VFL breaks down.

Related

How to add width constrain using constraintsWithVisualFormat while already using constraintsWithVisualFormat for vertical spacing between two view?

I am new to IOS and I am adding constrain to my added view with using constraintsWithVisualFormat. I already use constraintsWithVisualFormat to have vertical spacing by these following code:-
self.underlineConstraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"V:[view]-80-[underlineView(5)]"
options:NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing
metrics:nil views:views];
Here 5 is height of underlineView and 80 is space between two views but I want to add constrain of width of underlineView 150.
To add width constraints:
[self.view addConstraints: [NSLayoutConstraint
constraintsWithVisualFormat:#"H:[underlineView(150)]"
options: kNilOptions
metrics:nil views:views]];

Why do these Auto Layout constraints for a UIView prevent UIButton subviews from receiving touch events?

I'm using auto layout, programmatically. I've got a simple UIViewController with a few controls, including two UIButtons arranged side-by-side. I often group related controls within a UIView, to act as a container, making the arrangement of groups-of-controls a bit easier to manage. You'll see that below with _iapButtonsView, which holds the two buttons and some spacers.
My question. In the following example, I was caught out by what I thought was a valid change to the constraints, that actually resulted in the UIButtons not receiving touch events.
Code extract - constraints in which the buttons do receive touch events:
- (void)viewDidLoad
{
[super viewDidLoad];
...
_buyButton = [ViewCreationHelper createRoundedBorderButtonBold];
[_buyButton addTarget:self action:#selector(buyTap:) forControlEvents:UIControlEventTouchUpInside];
_restoreButton = [ViewCreationHelper createRoundedBorderButton];
[_restoreButton addTarget:self action:#selector(restorePurchaseTap:) forControlEvents:UIControlEventTouchUpInside];
_iapButtonsView = [UIView new];
_iapButtonsView.translatesAutoresizingMaskIntoConstraints = NO;
[contentView addSubview:_iapButtonsView];
...
[_iapButtonsView addSubview:_buyButton];
[_iapButtonsView addSubview:_restoreButton];
// Constraints
NSDictionary* views = NSDictionaryOfVariableBindings(scrollView, contentView, iapDesciption, _iapButtonsView, _buyButton, _restoreButton, spacer1, spacer2, spacer3);
...
constraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"V:|-25-[iapDesciption]-40-[_iapButtonsView]|"
options:0
metrics:nil
views:views];
[contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-20-[_iapButtonsView]-20-|" options:0 metrics:nil views:views];
[contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[spacer1][_buyButton(==120.0)][spacer2(==spacer1)][_restoreButton(==_buyButton)][spacer3(==spacer1)]|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:views];
[_iapButtonsView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_buyButton(==80.0)]|" options:0 metrics:nil views:views];
[_iapButtonsView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_restoreButton(==80.0)]|" options:0 metrics:nil views:views];
[_iapButtonsView addConstraints:constraints];
...
}
The constraints in question are the vertical constraints for _iapButtonsView. During development (this is an In-App Purchase screen) I had some debug controls at the bottom, which is why I had the trailing | connecting to the superview's bottom edge, like this:
constraintsWithVisualFormat:#"V:|-25-[iapDesciption]-40-[_iapButtonsView][someSpacer][someControls]|"
When I took those debug controls out, I changed those constraints to be:
constraintsWithVisualFormat:#"V:|-25-[iapDesciption]-40-[_iapButtonsView]"
thinking that was more correct: they're anchored from the top, only, the _iapButtonsView gets its size from its subviews (principally, the two buttons), so I shouldn't connect to the bottom edge of the superview...
With that change, the buttons no longer receive touch events. To experiment, I tried explicitly setting the vertical size of _iapButtonsView, but still not connecting to the bottom edge of the superview, e.g.
constraintsWithVisualFormat:#"V:|-25-[iapDesciption]-40-[_iapButtonsView(==80.0)]"
With those constraints, the buttons still do not receive touch events.
What am I not understanding?
(Edit: I removed the duplicated [contentView addSubview:_iapButtonsView]; in the code, above, per suggestion from daddy warbucks)
One issue, you've called this two times:
[contentView addSubview:_iapButtonsView];
Not sure if this helps, but it could be an issue.
Also, you don't have to use "(==80.0)", just use "(80.0)", or even "(80)" not sure if this helps, but hey, it could, right?

Align first line of multiple lines UILabel with UIImage using AutoLayout programmatically ( Objective-C )

I'm trying to align a multi-lines UILabel with a UIImage. The thing is : I want to align the first line of this label with the image, horizontally.
What I tried :
1 - align the image with the entire UILabel, so basically I still wanted to take the image up.
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[image(width)]-8-[multiLinesLabel]|" options:NSLayoutFormatAlignAllCenterY metrics:metrics views:views]];
2 - So I tried this, but then the image was too high.
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[image(width)]-8-[multiLinesLabel]|" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
Has anybody got an idea to do that ?
Thanks in advance
EDIT : I finally did the trick like that :
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[image(width)]-8-[multiLinesLabel]|" options:kNilOptions metrics:metrics views:views]];
[containerView addConstraint:[NSLayoutConstraint
constraintWithItem:image
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:multiLinesLabel
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:3.0]];
I'm not really proud of me, it's not a clean way of solving the problem, but it works. If anyone thinks about an other solution...
You could add a single line dummy label with the same font and a single character of text. Make it hidden. Position it to align with your image, and then position the multi-line label's top edge to align with the dummy label.

AutoLayout: Vertical to horizontal with no explicit sizes?

I am interesting in accomplishing something of this kind, using preferably only AutoLayout and no code:
Here are the constraints, that I need to maintain:
No explicit view sizes, as both iPhone and iPad should be supported.
View 1 maintains its square aspect ratio.
View 2 takes all the remaining space (vertical or horizontal) and resizes its subviews.
On rotation, change from vertical alignment to horizontal.
I would appreciate any suggestions on how something like this can be accomplished. AutoLayout seems a bit confusing for this kind of task.
Thanks a lot!
P.S. - As a more complicated follow-up, do you think something like this is achievable? I am a bit skeptical.
All of this is definitely possible with Auto Layout. It will just require some code!
To address each bullet point:
This is what AutoLayout is all about. Depending on your implementation, you should actually have a difficult time NOT making it work on both the iPad and iPhone.
No problem
[view1 addConstraint:[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:view1 attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];
Sure can
UIView *view1;
UIView *view2;
UIView *superview;
NSDictionary *metrics = #{#"border":#(VIEW1_BORDER)};
NSDictionary *views = NSDictionaryOfVariableBindings(view1,view2);
UIDeviceOrientation orientation = [[UIDevice currentDevice]orientation];
if (UIDeviceOrientationIsPortrait(orientation)) {
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[view1]-(border)-[view2]|" options:0 metrics:metrics views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view2]|" options:0 metrics:nil views:views]];
} else {
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[view1]-(border)-[view2]|" options:0 metrics:metrics views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view2]|" options:0 metrics:nil views:views]];
}
Implement the UIViewController willRotateToInterfaceOrientation:duration: method and use the same code from above. Just be sure to remove any previously added constraints before adding the new ones to avoid conflicting constraints. UPDATE: Since willRotateToInterfaceOrientation:duration: is deprecated in iOS 8, you could observe the default NSNotificationCenter for
UIApplicationWillChangeStatusBarOrientationNotification and
UIApplicationDidChangeStatusBarOrientationNotification
notifications to handle orientation changes.
Using three views would be nearly exact same code as above.

How to resolve ambiguous layout?

I want an iPad layout that that has two panels side by side, to fill the width of the screen and both are as tall as the screen. My attempts have led to as follows
self.view addConstraints:
#"|[_sidePanel(300)]-1.0-[_mainPanel]|"
#"V:|[_sidePanel]|"
#"V:|[_mainPanel]|"
Inside __sidePanel_ I'm trying to create more constraints on child views.
Note the _sidePanel view is a UIScrollView.
I want to stack 2 views on top of one another in the side panel.
So I add the following constraints to__sidePanel_.
_sidePanelView addConstraints:
#"|[_top(300)]|"
#"|[_bottom(300)]|"
#"V:|[_top]-5.0-[_bottom]|"
It seems I need to specify the width for these two views in order to avoid ambiguity.
But I want the bottom view to fill the remaining space of __sidePanel_.
If I just pin __bottom_ to the bottom of __top_ (which gets a defined height at some point based on its contents) and to the bottom of its parent __sidePanel_, the __sidePanel_ and __bottom_ are both ambiguous; which makes sense i guess since the constraints are awfully similar (and which doesn't get avoided by adding the constraint for __bottom_ to the __sidePanel_ view as opposed to the topmost self.view).
If I hardcode a height for __bottom_, i resolve ambiguity but I don't want a defined height; i want it to fill remaining space in __sidePanel_.
Any suggestions on what I could try to resolve ambiguity but still achieve what I'm after?
You need to specify a height for either top or bottom -- it sounds like top gets a defined height at some point, but you need set a defined height for it initially, which you can change later.
Also, there's no need to specify the widths (300) for either top or bottom, since you've pinned them to the sides of sidePanel, which itself has a defined width. so these constraints worked fine with no ambiguity:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_sidePanel(300)]-1.0-[_mainPanel]|" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_sidePanel]|" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_mainPanel]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_top]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_bottom]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_top]-5.0-[_bottom]|" options:0 metrics:nil views:viewsDict]];
self.topHeightCon = [NSLayoutConstraint constraintWithItem:self.top attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:300];
[self.top addConstraint:self.topHeightCon];
Later, when you calculate the actual height for top, you can use self.topHeightCon.constant = (some value) to adjust its height.
In my case it came down to the fact that the view I was trying to have subviews constrain to its bounds was a UIScrollView, which wasn't happening. I since changed it to a UIView and voila my constraints work. And there you have it.

Resources