Autolayout metrics differs between iOS 7 & 8 for UITableViewCells - ios

I have a custom UITableViewCell that contains only one UILabel that holds a static text, I wrote two Autolayout constraints in code (using the regular way but not visual format) to pin the leading and top edges of the label to the container view of the cell, and it works on both iOS 7 and iOS 8 devices but with a weird problem in iOS 8: the top edge of the label is 12 pixels far from the top edge of the container view (and this is the correct and expected metrics) but in iOS 8 is more than that, about 20 pixels, and the same shifting happens for the leading edge of the label.
what does this means ? is that related to UIKit changes between iOS 7 and iOS 8 for UITableViewCells which now contains a hidden scrollView in its hierarchy ?
p.s. the constraints are :
constraint = [NSLayoutConstraint constraintWithItem:self.myLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier: 1.0 constant:12];
[self.contentView addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.myLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:12];
[self.contentView addConstraint:constraint];
any help would be highly appreciated ...

In my case I needed to add a specific constraints to an imageview manually at runtime, the result was a different Leading alignment in iOS7 (completely attached to the left side of the cell) and iOS8 (correctly aligned). The issue was not only in the cell but even in the UITableView constraints:
1) check your tableview leading and trailing constraints, must be like those ones:
Both for Leading and Trailing, sometimes when you add manually a table view to the view controller the natural way to align the tableview's left and right sides is set it to -16, that works well for iOS 8 but not for iOS 7.
2) If you amend/add some constraints check if the cell respond to setLayoutMargins (only iOS8) and in case change the constraint's constant value:
CGFloat leftMargin = 8.0;
if ([self respondsToSelector:#selector(setLayoutMargins:)]) {
leftMargin = 0.0;
}
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.userImageView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeadingMargin multiplier:1 constant:leftMargin]];

Related

iOS- Programmatically set the left and right margins of a subview to be equal

I have a view in an iPad app that on full screen mode expands to fill the entire screen and is available both in portrait and landscape.
This view also contains many subviews (like rectangular cards) and a scrollview.
[ I programmatically add these to a content view, that is created programmatically (in a scroll view) calculating frame size of the cards accordingly ]
contentView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 640, 2000)];
[contentView addSubview:firstCard];
self.formScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.formScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.formScrollView.scrollEnabled=true;
self.scrollView addSubview:contentView];
self.scrollView.contentSize=contentView.frame.size;
My requirement is that despite orientation all the cards would be aligned one after the other with equal spaces from both the left and right margin.
The cards though of varying height are all equal in width.
So far, this is what I have tried to set them centered horizontally. I added this code snippet in the viewDidLayoutSubviews
[contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstCard
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
where contentView refers to the contentView in the full screen view controller and first card is the first subview card
I also set the property
firstCard.translatesAutoresizingMaskIntoConstraints = NO;
On running in the simulator, I get disastrous results. The view is weirdly offset and the other cards are all scattered everywhere else.
This does not happen when I do not set the constraints. They look ugly but they are still vertically aligned the way they have to be.
I am quite new to auto layout and iOS development.
I do wonder if any of these problems have to do with the fact that my contentView is a fixed frame?
I keep the contentView with such a fixed frame keeping in mind, the non full screen mode.
Where am I going wrong? I would really appreciate any help and advice.
Thanks.
EDIT :-
I also tried aligning my content view such that it remains centered to the scroll view. Here's code for that...
(That was also a disaster, the content view doesn't even appear on screen)
[self.formScrollView addConstraint:[NSLayoutConstraint constraintWithItem:contentView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.formScrollView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
}
[where formScrollView is the scroll View ]
You will also have to set the vertical constraint for card views. In your case each view's "top" (starting after first view) will have a relation with the earlier view's "bottom". So that they fall after each other vertically. Can be added as below.
float verticalSpacing = 20.0f;
nextCard.translatesAutoresizingMaskIntoConstraints = NO;
[contentView addConstraint:[NSLayoutConstraint constraintWithItem:nextCard
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[contentView addConstraint:[NSLayoutConstraint constraintWithItem:nextCard
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:previousCard
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:verticalSpacing]];

View is not getting stretched to edges of phone on landscape orientation? How to add constraints programmatically?

How to add constraints programmatically for views which should be placed next to each other on scroll view?
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view2 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view1 attribute:NSLayoutAttributeRight multiplier:1 constant:10];
[self.scrollView addConstraint:constraint];
constraints are basically a equation iOS will try to satisfy at runtime.
General form is:
item1.attribute = multiplier * (item2.attribute) + constant
In the code above:
view2.left = 1 * (view1.right) + 10
So view2's left will be at 10pt space from view1's right.

iOS Constrainsts: how to adjust UIButton width and height in a view?

I'm trying to make a keyboard. I have 9 buttons inside a view with: width and height <= 75. On iPhone 5 works perfectly. But the iPhone 4 buttons are stretched and the size is still 75. Could anyone help me?
The problem is that your telling the views (buttons) to have a height or width >= 75 which means you have an ambiguous layout (someone already mentioned this) - you can check for this by examining the hasAmbiguousLayout property of your view. It's likely not working on the iPhone 5 correctly either it just so happens that when you've run it autolayout has found the solution you're looking for so it appears to work. Run it enough times and eventually you'll probably get the undesired layout. Ah the joys of autolayout. Anyways one solution to this problem is to set the height and width of one button, and then tell all the other buttons to follow suite. The visual format language guide has an example of this but i'll show you what I mean.
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button1(75)[button2(==button1)[button3(==button1)]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(button1, button2, button3)]];
Then do something similar to lay them out vertically. When autolayout runs it should find the correct layout for them. The next problem comes in how you use it. If you did all this work in a subclass of UIView and then went ahead and made that views width something crazy like 400 pt wide then autolayout will break - in this case because we pinned to the left and right sides. To fix this problem I'd probably remove the the last | and not pin the right side of button3 to the right side of the superview.
The other option you have is to specify constrains using the long format. For Example
//set button1 width to 75
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:button1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:75]];
//set button2 width == button1
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:button2
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:button1
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0]];
//pin button1 to the left
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:button1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];
//pin button2 to button1
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:button2
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:button1
attribute:NSLayoutAttributeRight
multiplier:1
constant:0]];
Have you considered changing the height programmatically? Here is a stub of code I use.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
//iPad Code
}else{
if([[UIScreen mainScreen] bounds].size.height > 560){
//iPhone 5 Code
}else{
// iPhone 4 Code
}
}
First of all your basic design should be iPhone 4.
It will be much easier to increase the screen design than to shrink it.
Now, regarding your question, it depends on what you want to achieve.
(1) Do you want only the top and the bottom padding to change in different devices?
(2) Do you want the buttons to have different sizes in different devices?
(3) Do you want the spaces between the buttons to be different in different devices?
Anyway, you should set a constraints for equal width and equal height for all the buttons.
From now on, the easiest solution (1) is to put all the buttons in one container that will have a constant height and will be centered in the screen.
This way there will be lower or higher padding around the buttons on different devices.
If you want the spaces between the buttons to change (2) then you can create a matrix of views (imagine sudoku), make all the views have equal widths and heights (constraints) and zero space between them (also constraints), put a button in the center of each view (constraints).
This way the buttons will remain in the same size but the spaces between them will grow or shrink.
Let me know if you understand the above solutions.

Using Autolayout in IB how do I position a UIView in the center of the screen programmatically?

I have a UIView that I would like to center horizontally over the main view and then center vertically over the main view, minus about 14 pixels.
If I set it up with IB it works on the Retina 3.5, but when running on Retina 4 it is of course off by roughly 40 pixels.
I am thinking the best solution is establishing these constraints programmatically based on screen height?
Use the centerX and centerY constants, and put in a constant of 14 (or -14 not sure which way you want it to be),
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterY multiplier:1 constant:14]];
In order to make your view in Center(Horizontally & Vertically). Select the view in Interface Builder, then from Top Menu, Select Editor> Align> Horizontal Center in Container/ Vertical Center in Container.
You can also add more constraints to satisfy view's Geometry.
You don't need to set constraints programmatically to align a view to horizontal & vertical center of the screen. It can be done via Interface builder itself. However, you still can set constraints using NSLayoutAnchor apis in code, like the below example. NSLayoutAnchor apis are simple, elegant and, concise. And are available since iOS 9.
myView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
myView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)

Resetting a fixed width and height in AutoLayout

I'm creating a custom UIView called CTCycleClock with a subview called CTCycleSlider. It reacts to a gesture so it can rotate on one axis (like looking from above upon a roulette table).
To achieve this, the main view CTCycleClock creates two constraints on the CTCycleSlider subview that center it on X and Y.
Example:
Furthermore, the CTCycleSlider subview creates two constraints on itself that set a specific width and height. This is necessary because otherwise upon rotation, the disk will make itself larger.
This works nicely and correctly. But when the superview has a bigger size (for instance on iPad), I don't know how to tell AutoLayout that the subview has a new fixed width and height equal to the superview.
This is how I set constraints in the superview:
NSLayoutConstraint *centerX = [NSLayoutConstraint
constraintWithItem:subview
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.f constant:0.f];
NSLayoutConstraint *centerY = [NSLayoutConstraint
constraintWithItem:subview
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.f constant:0.f];
[self addConstraint:centerX];
[self addConstraint:centerY];
This is how I set constraints in the subview, where self.widthAndHeight is currently hardcoded to 320 on iPhone and 450 on iPad:
NSLayoutConstraint *w = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:self.widthAndHeight];
NSLayoutConstraint *h = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:self.widthAndHeight];
[self addConstraint:w];
[self addConstraint:h];
So my question is: how can I make a subview first hug the superview frame with a certain margin, but also set its width and height fixed?
EDIT: some clarifications as to why I need the constraint that sets width/height fixed.
When I won't set the width/height fixed, and the user touch-rotates the wheel, you get the following result:
In the above image, I've set constraints on the subview that set top/lead/width/height to the superview. That works great when the user hasn't rotated the wheel subview yet, but when they do, the autolayout constraints force the rectangular UIView smaller so it completely fits in the superview.
Thus the question remains: how can I create constraints that initially resize the subview correctly to the superview, but then set a fixed width/height so upon rotation, it stays the same size?
...how can I make a subview first hug the superview frame with a
certain margin, but also set its width and height fixed?
I don't understand your question. If you make your image view hug the superview with a fixed margin (on all sides) then the size of the image view is dictated by the superview.
You could pin the image view on 2 sides (e.g. top and left) and specify a size. Then the distance to the other 2 sides would vary based on the size of the superview. Or you could center it in the superview and fix the size, and then ALL The margins would vary based on the size of the superview.

Resources