Autolayout, programed vs interface builder - iphone

Just to add a view that lives in the same parent view as the other two related views.
self.vDist = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 42, 21)];
[self.vDist setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.vDist];
NSLayoutConstraint *lc;
lc = [NSLayoutConstraint constraintWithItem:self.vDist attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:20.0];
[self.view addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.vDist attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:20.0];
[self.view addConstraint:lc];
Then, I create the small squared view on top
/*********************** video view */
self.videoView = [[UIView alloc] initWithFrame:CGRectMake(90, 20, 140, 140)];
[self.videoView setBackgroundColor:[UIColor greenColor]];
[self.videoView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.videoView];
// width & height constraints, as UIViews don't have intrinsic constraints
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:140.0];
[self.videoView addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:140.0];
[self.videoView addConstraint:lc];
// center constraint
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
[self.view addConstraint:lc];
// top constraint with its parent view
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:20.0];
[self.view addConstraint:lc];
This squared view shows ok in any device orientation
Now I create the second view on the lower part of the screen. It really doesn't matter its dimensions here as I create a constraint for each size attribute
/*********************** listado view */
self.listadoView = [[UIView alloc] initWithFrame:CGRectMake(5, 248, 310, 100)];
[self.listadoView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.listadoView setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:self.listadoView];
// height and width constraints as UIView don't have intrinsic constraints
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:310];
[self.listadoView addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:300];
// make it weak to be able to squeeze contents
lc.priority = 250;
[self.listadoView addConstraint:lc];
// top with videoView's bottom
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.videoView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:88.0];
[self.view addConstraint:lc];
// top with parent's view top
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:248.0];
[self.view addConstraint:lc];
// center constraint
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
[self.view addConstraint:lc];
trying to replicate here what IB did with the constraints in the other view
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:5];
[self.view addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10];
[self.view addConstraint:lc];
Te resulting screens are below with the green square. The vertical green screen shows what I was expecting (see yellow images down below), but in the green horizontal, as you can see, the bottom edge of the listadoView is below the edge of the screen, even though I'm setting all the constraints IB sets for the yellow one, those along with the ones I set in the yellow one too.
Any advice on how to set the constraints?... maybe the order in which they are created has something to do with it?, because there were times when I removed a constraint and it worked, then I re-added that constraint and still worked!. Ran it again with all the constraints, and then stopped working (what?!). And it's been hard to successfully reproduce it in order to file a bug, but it happened.
I've tested this code in XCode 4.6 under iOS5.1 and 6.x and with xCode 5 under iOS6.1.

Found my error. I was setting the listadoView's bottom constraint with its parent view to be 10, that is +10, and it should be -10. With this, the interface shows as with the other .xib (with the yellow square).

Related

macOS 10.12 auto-layout issues

There are several huge changes in 10.12, but in my opinion the biggest is the new auto-layout system (or modified/rewritten old one ... who knows). I have prepared a simple project to demonstrate the issue. It's related with collapsing a split view item via a button. It happens only on 10.12. All you need to do is to compile the project and click the button. An error will be presented:
2016-10-04 15:10:28.284296 test-12[64932:7425277] [Layout] Detected missing constraints for . It cannot be placed because there are not enough constraints to fully define the size and origin. Add the missing constraints, or set translatesAutoresizingMaskIntoConstraints=YES and constraints will be generated for you. If this view is laid out manually on macOS 10.12 and later, you may choose to not call [super layout] from your override. Set a breakpoint on DETECTED_MISSING_CONSTRAINTS to debug. This error will only be logged once.
After setting a breakpoint we can find out that the problematic view is:
(lldb) po $arg1
<NSSplitDividerView: 0x618000161980>
Am I completely wrong or there is a definite problem with 10.12? And please advice how to prevent such type of errors if possible.
The project can be downloaded from github
Once again thank you for the help.
I. Nikolov
I have the same auto-layout issue and I found a workaround for my project.
After adding the subview with its constrains I call the window to "layout". Unfortunately I do that after each "add" call, otherwise the system will throw the ugly message again. It works for me and I am just calling it during initialistion.
// Left Split View
[self.scrollviewMain setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.viewMainLeft addSubview:self.scrollviewMain];
[self.viewMainLeft addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainLeft attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.scrollviewMain attribute:NSLayoutAttributeBottom multiplier:1.0 constant:l_floatConstant]];
[self.viewMainLeft addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainLeft attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.scrollviewMain attribute:NSLayoutAttributeTop multiplier:1.0 constant:-l_floatConstant]];
[self.viewMainLeft addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainLeft attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.scrollviewMain attribute:NSLayoutAttributeLeft multiplier:1.0 constant:-l_floatConstant]];
[self.viewMainLeft addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainLeft attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.scrollviewMain attribute:NSLayoutAttributeRight multiplier:1.0 constant:l_floatConstant]];
[self.windowMain layoutIfNeeded];
// Right Split View
[self.scrollviewDetails setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.viewMainRight addSubview:self.scrollviewDetails];
[self.viewMainRight addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainRight attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.scrollviewDetails attribute:NSLayoutAttributeBottom multiplier:1.0 constant:l_floatConstant]];
[self.viewMainRight addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainRight attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.scrollviewDetails attribute:NSLayoutAttributeTop multiplier:1.0 constant:-l_floatConstant-1]];
[self.viewMainRight addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainRight attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.scrollviewDetails attribute:NSLayoutAttributeLeft multiplier:1.0 constant:-l_floatConstant]];
[self.viewMainRight addConstraint:[NSLayoutConstraint constraintWithItem:self.viewMainRight attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.scrollviewDetails attribute:NSLayoutAttributeRight multiplier:1.0 constant:l_floatConstant]];
[self.windowMain layoutIfNeeded];

Why is constraint not working for UIView but works for UILabel?

I have a simple (complete) example here which seems very odd, but surely im just missing something small... right? Can you help debug the simple code below. This code makes the aView disappear, but when if I put the aLabel in the constraint where the aView is, it works perfect. Why? Thanks for any input, this seems crazy to me.
austin
UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(0, 100, 100, 30)];
aView.backgroundColor = [UIColor redColor];
aView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:aView];
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 100, 30)];
aLabel.backgroundColor = [UIColor redColor];
aLabel.text = #"Label";
aLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:aLabel];
NSLayoutConstraint *myConstraint =[NSLayoutConstraint
constraintWithItem:aView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
[self.view addConstraint:myConstraint];
myConstraint =[NSLayoutConstraint
constraintWithItem:aView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
[self.view addConstraint:myConstraint];
Well, the line aView.translatesAutoresizingMaskIntoConstraints = NO;
is making the view's size zero.. So you got to add these lines of code :
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:aView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100];
[aView addConstraint:widthConstraint];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:aView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:30];
[aView addConstraint:heightConstraint];
The answer is simple. Certain objects, like UILabels have an intrinsic size based on their contained text, UIViews don't. So, since you didn't set a size for your UIView, its size is 0. You need to add either size constraints, or pin the view to the sides of its superview (or other views in the same view hierarchy).
As an alternative, setting all 4 constraints (left, right, top, bottom) solves the problem as well. Width and height are then adjusted and the UIView will be stretched accordingly. Note that all four constraints must be set.

UIView to Ignore Orientation or Stick-To-Bottom Container

I want to display UIView on the bottom of the screen in portrait mode, so when a phone is rotated and horizontal orientation would reposition/resize all subviews, that one UIView would remain where it was, with the same size and original position (i.e. on the right end of horizontal orientation if it was on the bottom of portrait mode).
Is there a good way to do it?
You can set the autoresizing mask like this:
myView.autorezisingmask = UIViewAutorezingMaskFlexibleTopMargin;
This will keep the view at the bottom.
I can think of several ways to do this. One way, that I show below relies solely on using constraints. For this to work the 3 buttons should not be in their own transparent view, but just be subviews of the view you want to rotate (that is self.view in my example). I don't think the original constraints to these 3 buttons matters, because I remove them on rotation, but the constraints I started with had the center button with a centerX constraint, a fixed distance to the bottom, standard horizontal distances to the left and right buttons, and all three buttons had their baselines aligned. In viewDidLoad I loop through all of self.view's constraints and identify all that have to do with these buttons, and put them into an array so I can remove them and add them back later.
#interface ViewController ()
#property (strong,nonatomic) NSMutableArray *portraitConstraints;
#property (strong,nonatomic) NSMutableArray *landscapeConstraints;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.portraitConstraints = [NSMutableArray array];
self.landscapeConstraints = [NSMutableArray array];
for (NSLayoutConstraint *con in self.view.constraints) {
if (con.firstItem == self.leftButton || con.secondItem == self.leftButton || con.firstItem == self.centerButton || con.secondItem == self.centerButton || con.firstItem == self.rightButton || con.secondItem == self.rightButton) {
[self.portraitConstraints addObject:con];
}
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeRight:{
[self.view removeConstraints:self.portraitConstraints];
[self.view removeConstraints:self.landscapeConstraints];
[self.landscapeConstraints removeAllObjects];
NSLayoutConstraint *centerYCon = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *rightCon = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:-8];
NSArray *stackCons= [NSLayoutConstraint constraintsWithVisualFormat:#"V:[left]-[center]-[right]" options:NSLayoutFormatAlignAllLeading metrics:nil views:#{#"left":self.leftButton, #"center":self.centerButton, #"right":self.rightButton}];
[self.landscapeConstraints addObject:centerYCon];
[self.landscapeConstraints addObject:rightCon];
[self.landscapeConstraints addObjectsFromArray:stackCons];
[self.view addConstraints:self.landscapeConstraints];
break;
}
case UIInterfaceOrientationLandscapeLeft:{
[self.view removeConstraints:self.portraitConstraints];
[self.view removeConstraints:self.landscapeConstraints];
[self.landscapeConstraints removeAllObjects];
NSLayoutConstraint *centerYCon2 = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *leftCon = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeLeft relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:8];
NSArray *stackCons2= [NSLayoutConstraint constraintsWithVisualFormat:#"V:[left]-[center]-[right]" options:NSLayoutFormatAlignAllLeading metrics:nil views:#{#"left":self.leftButton, #"center":self.centerButton, #"right":self.rightButton}];
[self.landscapeConstraints addObject:centerYCon2];
[self.landscapeConstraints addObject:leftCon];
[self.landscapeConstraints addObjectsFromArray:stackCons2];
[self.view addConstraints:self.landscapeConstraints];
break;
}
case UIInterfaceOrientationPortrait:{
[self.view removeConstraints:self.landscapeConstraints];
[self.view addConstraints:self.portraitConstraints];
break;
}
default:
break;
}
}

iOS Autolayout: Resizing container with constraints

I have a scroll view with container view(self.tagScrollContentView) inside. That's in Storyboard. Then I generate buttons and place them inside container view with constraints programmatically.
for(NSInteger i = 0; i < allTags.count; i++) {
UIButton *tagBt = [[UIButton alloc] initWithFrame:(CGRect){CGPointZero, tagSize.width + 30, 17}];
[self.tagScrollContentView addSubview:tagBt];
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:nil multiplier:1.0 constant:tagSize.width + 30]];
if(prevBtRow1)
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:prevBtRow1 attribute:NSLayoutAttributeRight multiplier:1.0 constant:10.0]];
else
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.tagScrollContentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10.0]];
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.tagScrollContentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:7.0]];
prevBtRow1 = tagBt;
}
[self.tagScrollContentView addConstraints:constraintsArray];
[self.tagScrollView layoutSubviews];
This code put all buttons in a row depends of their width. All works fine. Then what I need is to enlarge tagScrollContentView to make all buttons become inside this view and not outside of bounds. Then to assign correct content size that is equal to container view to my scroll.
Unfortunately scroll doesn't work correctly. Content size doesn't fit container view.
The key issue is that your contentSize is not getting set because you're not adding that final trailing constraint from the last button to its superview. You can add one more constraint at the end, and your contentSize will be adjusted automatically:
for (NSInteger i = 0; i < allTags.count; i++) {
UIButton *tagBt = [[UIButton alloc] init];
tagBt.translatesAutoresizingMaskIntoConstraints = NO;
[self.tagScrollContentView addSubview:tagBt];
// add all of your constraints
prevBtRow1 = tagBt;
}
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:prevBtRow1
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.tagScrollContentView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:10.0]];
[self.tagScrollContentView addConstraints:constraintsArray];
There are a couple of unrelated issues:
I assume you had a tagBt.translatesAutoresizingMaskIntoConstraints = NO; line that didn't make it into your code sample.
There's no point in doing initWithFrame if you're going to be setting constraints. init is sufficient.
I'd suggest adding a height constraint to your button, too, so its constraints become unambiguous.
By the way, you're adding the button width constraint to the superview. It works either way, but generally you add a constraint to the nearest common parent and for the width constraint, that would be the button itself, not its superview.

Why are these constraints not working?

I´ve got a scrollview that I want to add in the viewDidLoad method to my superview. I do this with following code:
[self.view addSubview:self.scroll];
NSMutableArray *constraints = [NSMutableArray arrayWithCapacity:4];
[constraints addObject:[NSLayoutConstraint constraintWithItem:self.scroll
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:self.scroll
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1
constant:0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:self.scroll
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:self.scroll
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0]];
[self.view addConstraints:constraints];
The scrollview is intended to be placed at the bottom of the view and it's height should be half the height of the superview.
I get following error:
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)
(
"",
"",
"",
""
)
Will attempt to recover by breaking constraint
Can please someone tell me what I'm doing wrong ? Thank you.
rdelmar solved this question:
You could try adding [self.scroll setTranslatesAutoresizingMaskIntoConstraints:NO]