I'm new to XCode and all things iPhone and I noticed something which I assume is the correct behaviour but it seems a bit strange to me to adapt to. For reference:
XCode version 11.3
Storyboard device view: iPhone 11
When I create a new project and drop VerticalStackView into the main Storyboard view in the centre of it its size is automatically set to some reasonably sized rectangle container which I can freely and easily change by dragging the corners of it as I please:
Now, when I then drop a label into the the StackView at first everything seems ok
However an instant later, the StackView size automatically shrinks to the size of the just dropped in label which I can not change.
Inspecting the size of the StackView in its Size Inspector I can see the Width and Height to be set to fixed values (that reflect the fixed values of the label).
Anyone knows what is causing this automatic behaviour and how can I change it? I'm assuming this is the correct behaviour in a sense that StackView automatically changes its size to the size defined by the object dropped into it, however I'd like to do that later on once I'm happy with some basic look and I've added auto layout constraint rather than XCode doing some automatic for me. Is there any way I can modify this behaviour?
This seems like a somewhat odd approach. You want to add subviews to a UIStackView --- but you don't want the stack view to apply any layout attributes until you're "happy with some basic look" ... however, as soon as you then apply constraints, that look will change. Either the stack view will change size, or the subview will change size.
You could do this:
place your stack view where you want it
give it width and height and position constraints
add your subviews
This will prevent the stack view from changing size. As you add subviews, perhaps you decide the stack view needs to be taller... so edit the height constraint.
or...
place your stack view where you want it
In Size Inspector, change Layout from Automatic to Translates Mask Into Constraints
This will also prevent the stack view from changing size / position.
In both cases, though, the stack view's arranged subviews will continue to be affected by the stack view's layout properties. And, it seems like you will have much more difficulty switching to auto-layout and applying the correct constraints.
Related
I have a UIStackView with three labels whose height is determined using Dynamic Type and text that have can wildly varying lengths. The container for the stack view has a fixed width and height depending on device screen size (small on iPhone SE, for example.) I want to center the stack view within the container (with some outer margins.)
The problem is that depending on the font size and container height, some of the labels in the stack view will be clipped. Here is an example with the third label:
I have experimented with layout constraint priorities for both the stack view and the labels, but this doesn't appear to be the right approach. Instead setting the visibility of the labels works better: correct spacing between elements is maintained.
My question is then what is the right time to detect that the label's height isn't fully displayed and to hide it.
The label height is close to, but not exactly equal to the UIFont's lineHeight so there's some rounding involved that makes this a little difficult.
The biggest problem is that after a layout pass in the UIStackView's layoutSubviews the heights of the arranged subviews can be detected, but you can't hide the arranged views at that point because it causes another layout pass and recursion.
So what am I missing? :-)
Here's a test project - build for iPhone Xs in the Simulator and you'll see the same results in the screenshot above.
Solution
Tom Irving's gist below pointed me in the right direction. The trick is to enumerate the subviews after a layout pass and remove them if height requirements aren't met.
The updated project shows how to do this in DebugStackView's layoutSubviews. And yes, UIStackView is a worthy adversary.
Could you act on viewDidLoad?
My intuition would be to add up the height of all visible subviews in the stack view and then hide the last if there's a problem.
In the sample you've provided I would recommend getting a CGSize with [self.firstLabel textRectForBounds:self.view.bounds limitedToNumberOfLines:0] for each visible label, making sure to take the margin between items into acount, and determining if the total height is greater than the constant height you've assigned to the stack view. If so, hide the elements that go beyond the stack view's height.
Of course, there might be more to the problem than I understand, but that would allow you to calculate before the layoutSubview pass happens.
I was trying to practice Auto Layout in iOS, and I started with very simple UI. Please see image for understanding my problem.
All the text files are in middle of screen (I have deliberately kept on guide line), still you can see in preview, controls are not fully shown. I have not chosen specific size. Size is 'Inferred' still I am not able to see all the controls on UI.
I tried both adding and removing Auto Layout, but no luck. What should I do to create generic UI which will work with all the sizes of iPhone and iPad.
This image is without use of Auto Layout.
After enable autolayout and size classes you have to apply autolayout constraints.
Autolayout is a detail topic. Few basic things when applying autolayout is:
UI element need four constraints.
position x
Position y
height
width
So you will select first label (Number 1). Then press control and drag to superview. You will be provide options. Select Leading space (This will handle x position)
This is the way you can press control and drag:
http://www.appcoda.com/wp-content/uploads/2014/07/auto-layout-login-trailing.gif
Go to size inspector. You can see the constraint.
Press edit and change its value to 25(for test).
similarly control and drag again to superview and select Top space. (This will set y position for label)
This is simple way for the above taken from AppCoda
http://www.appcoda.com/wp-content/uploads/2014/07/auto-layout-control-drag.gif
You can change the value of these constraints according to your need.
UILabel and uitextfield get width and height from their content size. So don't need width and height constraints.
Now when you preview on any device this label will be stick on top left side of screen.
So this is a complete mechanism. You have to apply constraint to every ui element.
Below is a link to very comprehensive tutorial by
http://www.raywenderlich.com/115440/auto-layout-tutorial-in-ios-9-part-1-getting-started-2
At start this tutorial tried to create three views using autolayout. At the end it shows very similar scenario like yours by applying constraints to button and labels.
The problem here is that your constraints are not set correctly to work with every size of iPhone and iPad. You are setting the leading edge constraint to be a fixed size from your view controller's view to the subviews.
The simplest way to solve this issue would be to have a container view that you center in the view controller's view and then use constraints to set 'Center X Alignment Constraint' and 'Center Y Alignment Constraint' to set the container view's center to that of the view controller and then add your subviews to the container view.
As a side note auto layout has a reputation of being hard to learn, you have to put the time in to learn it, I would start with Apple's Auto Layout Guide.
I am learning constraints and spent whole day applying them to the following screen.It is not getting displayed properly in landscape mode.Basically i am not getting how to fix vertical space between ,say, label-Welcome to BBBB and textfield-username so that textfield always appears below the label yet the spacing between them is adjusted according to the screens of different size. If i go for Pin\Vertical space, it automatically fixes the constant value.
Remove the label (or just move it out of the way).
Fill the space that you want to resize with a view.
Constrain this view to the objects above and below and to the sides of the parent view.
Put your label into this view and constrain it to the top of this view and centred to it.
You may need to change the constraints on the objects above and below it to stop them from changing height in an unwanted manner.
This new view should now resize as the device changes orientation but the label should remain at the top of it.
Repeat on other areas of your layout (i.e put things that are not moving around as you want them into sub views and constrain these views to resize accordingly). Experiment with using variable heights, fixed heigh constraints and 'equal heights with' constraints on the views that you add to get them to resize as you need.
Edit: but you have a lot of vertically stacked items in that view - you may never get them all to fit into a horizontal orientation. You may need to remove a few, or add a scroll view or force that view only to layout in portrait mode or... Don't forget to check that it works on all devices you are targeting.
#Ali Beadle was right. Since i had a lot of vertically stacked items, lining them up in landscape mode was not possible. So, i went for scrollview.
I have taken a ScrollView first and then a UIView named ContentView on that ScrollView. I have made the width of ContentView equal to the width of parent UIView and kept its height fixed to 568. In my code i set
_contentViewHeight.constant = self.view.frame.size.height;
Now it scrolls in landscape mode while in potrait mode, it does'nt scroll.
I run into Autolayout problems all the time. But I finally figured out a way to overcome a lot of issues that arise from it.
I simply will use a container-View like ScrollView or even a plain old UIView, place all my controls in it. thats it. this makes things a lot easier for autolayout to figure out the constraints. in your case you could just use a UIView.
-start off by removing all the constraints you have I would start by selecting each control in the XIB and see if it has width/height constraint, select it then press the delete key then at the bottom of the project explorer you'll see all the constraints that auto layout has select each one then delete. that should get rid of any complaints that auto-layout might have.
-Place a UIView object inside your main View then
-move all the controls inside it. Then
-add the necessary constraints for the container view so it'll resize in different orientations and don't worry about any constraints inside the container view (auto layout will figure them out automatically).
that does the trick for me usually.
I have been struggling for days with this implementation, and even though I have tried to do every tutorial I found on the web, I still cannot make things work the way I want.
Basically, I am trying to put my login form in a scrollview, so that it takes the whole screen at first (and on all iPhones / iPads), and if the keyboard appears everything should move. The problem IS, my view doesn't take the whole screen... Either it is too large, or too high, even though in Interface Builder everything seams correct (from layout to constraints). Below and image of the layout I want to achieve (I am using an universal storyboard, with Size Classes and Autolayout enabled):
http://img4.hostingpics.net/pics/829115app.png
Can someone point me out on achieving this layout ?
Thanks in advance.
I would suggest pinning top, leading and trailing spaces of your scroll view to its superview. And set a bottom space constraint less or equal to the keyboard's height if you set it to 0, the scroll view won't be able to resize.
With your form layout set vertical center constraints and top space to superview constraints for your top label being more or equal than the distance you set in the IB, and then you can set relative space constraints between each of the components.
Hope I answered your question.
Edit: Just the provided project and got it working. I think the problem is caused by it being a containerView inside a scrollView. And both the container and the scrollViews content view adapt to the size of its subviews. Because of that, setting relative constraints won't help.
What I did was to set an explicit size (screen's size) to the containerView and setting setTranslatesAutoresizingMaskIntoConstraints(true) to it.
I modified your project and uploaded it here
I cant for the love of god the the hang of this resizing superview.
I have a UIView *superview with 4 UILabels. 2 function as header for the 2 others.
The content in all 4 are dynamic coming from database.
SizeToFit vs SizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.
I use autolayout programatically and have set the superview height to be equal or greater to a dummy number.
where and how do I use these SizeToFit vs sizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize. I have read a lot of tips here on stack but ended up with nothing.
DO I need to recalculate the constraints for the superview somewhere specific. Maby setting the height to be ยด#property` in its controller class and remove and readd it?
Atm I have tried to put everything everywhere and then some. Still I get the same size end result with the dummy height and text floating outside. Even after setting clipsToBound on subview.
I am scratching my hair of.. help
If you're using Auto Layout, here's what you need to do:
Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.
Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.
Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.
That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)
Use container views
In the following example I have a 30x30 image, and the UILabel is smaller than the containing view with the placeholder text. I needed the containing view to be at least as big as the image, but it needed to grow to contain multi-line text.
In visual format the inner container looks like this:
H:|-(15.0)-[image(30.0)]-(15.0)-[label]-(15.0)-|
V:|[image(30.0)]|
V:|[label(>=30.0)]|
Then, set the containing view to match the height of the label. Now the containing view will ride the size of the label.
As #smileyborg pointed out in his answer, connecting the content rigidly to the superview informs the layout engine that the simple container view should cause it to grow.
Yellow alignment rectangles
If you want the yellow alignment rectangles add -UIViewShowAlignmentRects YES in your scheme's list of run arguments.
This almost follows #smileyborg answer and comes with a concrete example.
Won't describe all constraints, but those related to the calculation of the height of UI objects.
[Label] Labels must not have a fixed height constraint, in this case, AutoLayout won't resize labels to fit the text, so setting edge constraints is the key. (green arrows)
[Subview] Steps 1 and 3 are very easy to follow, but this step can be misunderstood. As in the case with labels, subviews must not have height constraint set. All subviews must have top constraint set, ignoring bottom constraint, which can make you think will trigger unsatisfied constraint exception at runtime, but it won't if you set bottom constraint for the last subview. Missing to do so will blow the layout. (red arrows)
[Superview] Set all constraints the way you need, but pay big attention to the
height constraint. Assign it a random value, but make it optional, AutoLayout will set the height exactly to fit the subviews. (blue arrows)
This works perfectly, there is no need to call any additional system-layout update methods.
This was made dramatically easier with the introduction of Stack Views in iOS 9. Use a stack view inside your view to contain all your content that resizes, and then simply call
view.setNeedsUpdateConstraints()
view.updateConstraintsIfNeeded()
view.setNeedsLayout()
view.layoutIfNeeded()
after changing your content. Then you can get your new size by calling
view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
if you ever need to calculate the exact size required for a view.