iOS 11 UIToolBar automatic expansion - uitoolbar

I'm creating a UIToolBar programmatically, it works fine except for iPhone X where I'd like the toolbar to expand depending on phone's orientation. The expansion works if toolbar was created using the interface builder.
I'm not setting any height constraint, even implicitly via autolayout mask.
Code:
UIToolbar* toolbar = [[UIToolbar alloc] init];
toolbar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:toolbar];
[self.view addConstraints:#[
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeLeft multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeBottom multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeRight multiplier:1 constant:0]]];
So, what should I do make the toolbar behave properly?

Ok, the partial answer is to use the safe area self.view.safeAreaLayoutGuide, or with context:
[NSLayoutConstraint constraintWithItem:self.view.safeAreaLayoutGuide attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeBottom multiplier:1 constant:0],
The 'partial' part refers to the fact that IB toolbar has height of 49, and manual toolbar has 44.
It's good enough for me, but if someone is able to fix/explain the 44-49 difference, I'll mark it as a solution.
Edit: I've found the missing part.
I wasn't calling invalidateIntrinsicContentSize. Adding a call from willTransitionToTraitCollection: withTransitionCoordinator: fixed that. Somehow, for last year or so, I've missed that the toolbar was changing its size in compact mode.

Related

NSLayoutconstraint created programmatically fails

I am on Xcode 8.3.3, OSX not iOS.
I have a customView I use as container to replace other views inside it.
The dark blue box on the right represents the container:
The constraints of the container are set in IB as follows where "scrollView" is the tables scroll view left of it:
This enables vertical scaling (scrollView is allowed to scale vertically) which is the desired behaviour:
Now I add a subview to the container with this code:
NSStoryboard *storyBoard = [NSStoryboard storyboardWithName:#"Main" bundle:nil];
_previewViewController = [storyBoard instantiateControllerWithIdentifier:#"showSHPreviewViewController"];
CGRect newFrame = CGRectMake(0, 0,CGRectGetWidth(_previewViewController.view.bounds),CGRectGetHeight(_previewViewController.view.bounds));
_previewViewController.view.frame = newFrame;
[self.previewContainer addSubview:_previewViewController.view];
This works as expected:
My Problem is, I want the subview to be stretched so it fits the whole height of its container. Therefore I have to set NSLayoutConstraints programmatically. But i don't get the logic.... Whatever I try leads to a behaviour where neither the scroll view nor the container can scale vertically at all.
This was my last try before I wrote this post:
[self.previewContainer addConstraint:[NSLayoutConstraint
constraintWithItem:_previewViewController.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.previewContainer
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0]];
[self.previewContainer addConstraint:[NSLayoutConstraint constraintWithItem:_previewViewController.view
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.previewContainer
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
[self.previewContainer addConstraint:[NSLayoutConstraint constraintWithItem:_previewViewController.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.previewContainer
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
I can confirm that the content of the added subview is setup correctly and resizeable (no "height-lock").

Xcode 6 Autolayout Subview Constraints

Here's my storyboard.
So you can see I made my main view background gray for debugging purposes. Inside that I have a view I call the 'Graph View' which is currently acting like a layout guide/container for a Chart (subclasses UIView). I've done constraints on the Graph View so it's (more or less) centered and has variable width/height depending on the device, seen in the Assistant Editor like so:
I init the chart using the Graph View's bounds.height and bounds.width however that's going to be the size in the interface builder, which is 568x500.
After I init and configure the chart I want to add to the Graph View, I have the following code
in an attempt to give my chart subview the same layout/visual properties as the Graph View "guide":
lineChart.backgroundColor = [UIColor clearColor];
lineChart.translatesAutoresizingMaskIntoConstraints = NO;
[self.graphView addSubview:lineChart];
NSLayoutConstraint *width =[NSLayoutConstraint
constraintWithItem:lineChart
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.graphView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:1];
NSLayoutConstraint *height =[NSLayoutConstraint
constraintWithItem:lineChart
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.graphView
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:1];
NSLayoutConstraint *top = [NSLayoutConstraint
constraintWithItem:lineChart
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.graphView
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:0.f];
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:lineChart
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.graphView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.f];
[self.graphView addConstraint:width];
[self.graphView addConstraint:height];
[self.graphView addConstraint:top];
[self.graphView addConstraint:leading];
[self.graphView layoutIfNeeded];
[lineChart layoutIfNeeded];
[lineChart strokeChart];
The results are the following on the iPhone 5s and the iPad Air
I want the chart to fill the space of its container completely.. expand when in iPad and shrink when on the 5s.
Any idea what's wrong with my code?
Thanks for your help!
EDIT
Output from the Debug View Hierarchy iPhone 5s and iPad Air respectively.

Set position, height, width auto layout?

I am just switching over to auto layout and before I was using:
[Btn setFrame:CGRectMake(165,164,135,35)];
But I know that for auto layout you don't use set frame. I know it's supposed to be something like:
self.constraintToAnimate = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:0.25
constant:0.0];
and this:
[self.scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|-[Btn(==300)]-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(Btn)]];
but I am not sure what each of these do or how the constraintsWithVisualFormat string means/ how it works.
If you could show me how to set the position, height and width of the Btn I would really appreciate it.
Thanks in advance.
Ok, I have to extrapolate a bit about the layout you are trying to achieve since you didn't exactly specify it. I am assuming you have a UIScrollView with a button as a subview that complicates things.
Since you are new to constraints, let's start with a simpler case, which is a UIButton which is a subview of a UIView. A UIButton already knows what its width and height should be based on its content, so you only need to specify its x and y location (2 constraints). I personally prefer the equation form of writing constraints rather than the visual format, so I'll use that. What you want is:
button.x = button.superview.x*1 + 0;
button.y = button.superview.x*1 + 0;
.x is a leading constraint (leading is from the left) and .y is a top constraint (from the top).
Next, since the constraint is between a child and it's superview, you add the constraints to the superview
[button.superview addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:button.superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:164.0]];
[button.superview addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:button.superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:165.0]];
This would position your button at (164,165) and the width and height of your button would come from the intrinsic size of the button (based on it's image / text).
So this is the simple case. Now let's turn to the scenario where your button.superview is a UIScrollView. This is more complicated because you need to think of constraints with a UIScrollview with a different mindset, namely like it is a fixed window size (the frame) into a landscape of infinite size (contentSize).
So let's say you had a UIView --> UIScrollView --> UIButton, where the UIView has a child scrollview, which in turn has a UIButton as a subview. Take this one step at a time.
1.) UIView --> UIScrollView
Constraints you write on this pair are between the frame of the UIView and the frame of the UIScrollView. This is very easy since the frame is a fixed size. So let's say you wanted the UIScrollView to be the full size of the UIView it was embedded in. You would write 4 constraints to specify it's X,Y,width and height. There are many ways to do this of course, I will list one.
scrollview.x (scrollview.frame.x) = scrollview.superview.x*1 + 0;
scrollview.y (scrollview.frame.y) = scrollview.superview.y*1 + 0;
scrollview.width (scrollview.frame.width) = scrollview.superview.width*1 + 0;
scrollview.height (scrollview.frame.height) = scrollview.superview.height*1 + 0;
In constraints, that would be:
// Let's constrain the scrollview to stick to the top and left of it's superview
[scrollView.superview addConstraint:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
[scrollView.superview addConstraint:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];
// Let's constrain the scrollview to make its width and height equal to its superview
[scrollView.superview addConstraint:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
[scrollView.superview addConstraint:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];
Ok, now the scrollview knows how to position its frame. That was the easy part.
2.) Situate the button inside the scrollview.
Ok, now you have to change your thinking. Since the scrollView is the parent of the button, you cannot constrain to the frame of the scrollView, but rather have to constrain to the contentView of the scrollView. This is theoretically an infinite plane.
So how can you constrain a view in an infinite plane? There is no setting in UIScrollView to do such a thing, so are we stuck?
No, what we need to do is define a content view for our UIScrollView. Let's drag ANOTHER view into the UIScrollView, and place the button in that view. Our view hierarchy should look like this now.
UIView (topView) --> UIScrollView --> UIView (contentView) --> UIButton (all nested).
Now we need to set up constraints to define the size of the contentView. Let's say that the contentView is the same size as the topView. Let's add constraints to make it so. The following 4 constraints are saying that the content view is the plane of the scrollview.
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0]];
But this is not enough because we said the scrollview was basically a window into an infinite plane. So we need 2 more constraints on our contentView to specify it's width and height. Let's just make them the same as the top level view. Notice that we are setting the constraints up between the contentView and the topView, bypassing the ScrollView altogether.
// You could make the constant parameter non-zero if you wanted it to be bigger/smaller (+/-) than the view
[topView addConstraint:[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:topView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
[topView addConstraint:[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:topView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];
Now we can finally define constraints on the button, which can be written as below so you follow along, or can be written as defined at the very top, since contentView == button.superview.
[contentView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:164.0]];
[contentView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:165.0]];
Now we should have properly set up all the constraints. To force a layout, just call [topView layoutIfNeeded] Hopefully this was clear, and let me know if you are having any problems. I might have missed something as I had to write this off the top of my head :)

Autoresize titleView in a NavigationBar with autolayout

I want a custom UIView in my UINavigationBar between a left and a right BarButtonItem but as wide as possible. For this reason I added a UIView in IB to the NavigationBar. With autolayout disabled everything works as expected with the autoresizing masks.
But in my storyboard with autolayout enabled I just cann't get it to work. It doesn't look like I can set any constraints in IB for the titleView. If I rotate my device to landscape mode, the UIView has still the same width.
What do I have to do so that the titleView fills the space between my UIBarButtonItems with autolayout enabled?
Thank you for any help
Linard
I solved the issue as following in my code:
- (void)willAnimateRotationToInterfaceOrientation:(__unused UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
CGSize navigationBarSize = self.navigationController.navigationBar.frame.size;
UIView *titleView = self.navigationItem.titleView;
CGRect titleViewFrame = titleView.frame;
titleViewFrame.size = navigationBarSize;
self.navigationItem.titleView.frame = titleViewFrame;
}
I haven't found another solution (with automatic resizing), but I'm open for new and/or better solutions
Linard
You can add constraints in code.
In viewDidLoad, you can do something like this:
UIView *titleView = [[UIView alloc] initWithFrame:CGRectZero];
self.navigationItem.titleView = titleView;
UIView *titleViewSuperview = titleView.superview;
titleView.translatesAutoresizingMaskIntoConstraints = NO;
[titleViewSuperview addConstraint:[NSLayoutConstraint constraintWithItem:titleView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:titleViewSuperview attribute:NSLayoutAttributeLeading multiplier:1 constant:0]]; // leading
[titleViewSuperview addConstraint:[NSLayoutConstraint constraintWithItem:titleView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titleViewSuperview attribute:NSLayoutAttributeTop multiplier:1 constant:0]]; // top
[titleViewSuperview addConstraint:[NSLayoutConstraint constraintWithItem:titleViewSuperview attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:titleView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; // width
[titleViewSuperview addConstraint:[NSLayoutConstraint constraintWithItem:titleViewSuperview attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:titleView attribute:NSLayoutAttributeHeight multiplier:1 constant:0]]; // height

Positioning UIView in UIViewController using NSLayoutConstraints causes aliased UILabel?

I'm trying to manually animate in a UIView, contained in a UIViewController, from the top of the screen. This view will have a height of 150px.
I setup layout constraints for when it's "collapsed" and when it's "expanded" (shown). I animate between the two constraints when I want to hide/show, respectively.
I setup the view contained in this UIViewController in IB with a simple UILabel in the upper-left of the view.
CGFloat closeUpViewHeight = 150.f;
self.closeUpViewController = [[[CloseUpViewController_Phone alloc] initWithNibName:#"CloseUpViewController_Phone"
bundle:nil]
autorelease];
[self addChildViewController:self.closeUpViewController];
[self.view addSubview:self.closeUpViewController.view];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.closeUpViewController.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeHeight
multiplier:0.f
constant:closeUpViewHeight]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.closeUpViewController.view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1.f
constant:0.f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.closeUpViewController.view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.f
constant:0.f]];
self.expandedCloseUpViewConstraint = [NSLayoutConstraint constraintWithItem:self.closeUpViewController.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:0.f
constant:0.f];
self.collapsedCloseUpViewConstraint = [NSLayoutConstraint constraintWithItem:self.closeUpViewController.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.f
constant:-1.f];
Anyone know why the resulting UILabel has aliasing problems?
Fixed my problem, pretty dumb mistake.
Inside the setup for my view controller, I added a drop shadow to the UIView and accidentally had carried over this:
self.view.layer.masksToBounds = NO;
self.view.layer.shadowOffset = CGSizeMake(0, 2);
self.view.layer.shadowRadius = 3;
self.view.layer.shadowOpacity = 0.8;
self.view.layer.shouldRasterize = YES; //<--- Don't rasterize.
Removing the shouldRasterize line fixes the issue here.