UIStackView Spacing - Don't push to top and bottom anchors - ios

I am trying to distribute some nested stack views and I think I'm missing a property to help me align the various views the way I want them.
Here's the current output:
The issue with this is that the two arranged subviews that are added to each column (a stackview), are distributed so that the first subview aligns to the top, and the second subview aligns to the bottom (leaving a variable space in between them).
But here's what I am hoping for - always a fixed space (say 10 px) in between the first and second arranged subview in each column, and the extra space below the second arranged subview is just left to be what it needs to be.
The view is arranged as follows:
outerStackView = the green view: (20px off top, 64px off left, 20px off bottom,
64px off right - present in both screenshots but only highlighted on the top one) with properties:
outerStackView.axis = .Horizontal
outerStackView.distribution = .FillEqually
outerStackView.spacing = 10
leftStackView, middleStackView, rightStackView added to the outerStackView each with properties:
columnStackView.axis = .Vertical
columnStackView.distribution = .Fill
columnStackView.alignment = UIStackViewAlignment.Top
columnStackView.spacing = 10
Then each column has 2 stackViews inside of it, represented by the darker gray box directly around the red and blue boxes. With properties:
redBlueStackView.axis = .Horizontal
redBlueStackView.distribution = .FillProportionally
redBlueStackView.alignment = UIStackViewAlignment.Top
redBlueStackView.spacing = 4

You should pin your stack view to you containing view. That will give you a more consistent look. You can also best all three of your stack view in a horizontal stack view them selves if you want all 3 to be equal sizing. Another tip is to mess with the content hugging priority and compression resistance variables. Hope that helps let me know if you have more questions.

Related

Custom distribution for UIStackView

I have the following UIStackView.
Here is the pseude-code:
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fill
Blue, Yellow, Violet and Green views have their correct sizes (autosized labels or buttons). However, red one is being used to fill the remaining space.
I want that Violet one to fill the empty space instead of Red. Is there any way to specify which view should fill the empty space?
You'll need to weight each view using contentHuggingPriority.
Try making the red view's contentHuggingPriority higher than the violet.

UIScrollView with dynamically sized content

(Xcode 11, Swift)
Being a newbie to iOS and Autolayout, I'm struggling with implementing a fairly simple (IMHO) view which displays a [vertical] list of items. The only problem is that items are decided dynamically and each of them could be either text or image (where either of those could be fairly large so scrolling would be required). WebView is not an option, so it has to be implemented natively.
This is how I understand the process:
Make in IB a UIScrollView and size it to the size of the outer frame.
Make a container view as a subview of UIScrollView (again, in IB) and size it the same.
Set constraint on equal width of both
At runtime, populate container view with UILabels/UIImageViews and also set constraints programmatically to ensure proper layout.
"Tell" scrollview about the subview height in order to make it manage the scrolling thereof.
Is this the right approach? It doesn't seem to work for me (for a toy example of dynamically adding a very tall image to a container view - I cannot get the scrolling to work). What would be the proper way to do the last step in the process above - just force the contentSize of the scrollview to the size of the populated container view (it doesn't seem to work for me). Any help would be appreciated.
When adding multiple elements to a scroll view at run-time, you may find it much easier to use a UIStackView... when setup properly, it will automatically grow in height with each added object.
As a simple example...
1) Start by adding a UIScrollView (I gave it a blue background to make it easier to see). Constrain it to Zero on all 4 sides:
Note that we see the "red circle" indicating missing / conflicting constraints. Ignore that for now.
2) Add a UIView as a "content view" to the scroll view (I gave it a systemYellow background to make it easier to see). Constrain it to Zero on all 4 sides to the Content Layout Guide -- this will (eventually) define the scroll view's content size. Also constrain it equal width and equal height to the Frame Layout Guide:
Important Step: Select the Height constraint, and in the Size Inspector pane select the Placeholder - Remove at build time checkbox. This will satisfy auto-layout in IB during design time, but will allow the height of that view to shrink / grow as necessary.
3) Add a Vertical UIStackView to the "content view". Constrain it to Zero on all 4 sides. Configure its properties to Fill / Fill / 8 (as shown below):
4) Add an #IBOutlet connection to the stack view in your view controller class. Now, at run-time, as you add UI elements to the stack view, all of your "scrollability" will be handled by auto-layout.
Here is an example class:
class DynaScrollViewController: UIViewController {
#IBOutlet var theStackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// local var so we can reuse it
var theLabel = UILabel()
var theImageView = UIImageView()
// create a new label
theLabel = UILabel()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theLabel.translatesAutoresizingMaskIntoConstraints = false
// multi-line
theLabel.numberOfLines = 0
// cyan background to make it easy to see
theLabel.backgroundColor = .cyan
// add 9 lines of text to the label
theLabel.text = (1...9).map({ "Line \($0)" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .yellow
// add 5 lines of text to the label
theLabel.text = (1...5).map({ "Line \($0)" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// create a new UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load an image for it - I have one named background
if let img = UIImage(named: "background") {
theImageView.image = img
}
// let's give the image view a 4:3 width:height ratio
theImageView.widthAnchor.constraint(equalTo: theImageView.heightAnchor, multiplier: 4.0/3.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .green
// add 2 lines of text to the label
theLabel.text = (1...2).map({ "Line \($0)" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load a different image for it - I have one named AquariumBG
if let img = UIImage(named: "AquariumBG") {
theImageView.image = img
}
// let's give this image view a 1:1 width:height ratio
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor, multiplier: 1.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
}
}
If the steps have been followed, you should get this output:
and, after scrolling to the bottom:
Alignment constraints (leading/trailing/top/bottom)
The alignment constraint between Scroll View and Content View defines the scrollable range of the content. For example,
If scrollView.bottom = contentView.bottom, it means Scroll View is
scrollable to the bottom of Content View.
If scrollView.bottom = contentView.bottom + 100, the scrollable
bottom end of Scroll View will exceed the end of Content View by 100
points.
If scrollView.bottom = contentView.bottom — 100, the bottom of
Content View will not be reached even the scrollView is scrolled to
the bottom end.
That is, the (bottom) anchor on Scroll View indicates the (bottom) edge of the outer frame, i.e., the visible part of Content View; the (bottom) anchor on Content View refers to the edge of the actual content, which will be hidden if not scrolled to.
Unlike normal use cases, alignment constraints between Scroll View and Content View have nothing to do with the actual size of Content View. They affect only “scrollable range of content view” but NOT “actual content size”. The actual size of Content View must be additionally defined.
Size constraints (width/height)
To actually size Content View, we may set the size of Content View to a specific length, like width/height of 500. If the width/height exceeds the width/height of Scroll View, there will be a scrollbar for users to scroll.
However, a more common case will be, we want Content View to have the same width (or height) as Scroll View. In this case, we will have
contentView.width = scrollView.width
The width of Content View refers to the actual full width of content. On the other hand, the width of Scroll View refers to the outer container frame width of Scroll View. Of course, it doesn’t have to be the same width, but can be other forms like a * scrollView.width + b.
And if we have Content View higher (or wider) than Scroll View, a scrollbar appears.
Content View can not only be a single view, but also multiple views, as long as they are appropriately constrained using alignment and size constraints to Scroll View.
For details, you may follow this article: Link.

UIStackView; Equal Spacing Between & Outside Elements

UIStackView is awesome, I love Equal Spacing Distribution.
But how to achieve the same space also outside of elements dynamically?
In my case all elements will have same ratio 1:1
You can add equal spacing using the story board as shown here:
Source: https://stackoverflow.com/a/32862693/3393964
#Declan has the right idea. Here's that answer programatically where you add extra views on either side so the stack view gives correct outside spacing with any number of buttons.
stackView.alignment = .center
stackView.axis = .horizontal
stackView.distribution = .equalCentering
// Then when I add the views...
let leftView = UIView()
stackView.addArrangedSubview(leftView)
content.buttons.forEach { (button) in
stackView.addArrangedSubview(button)
}
let rightView = UIView()
stackView.addArrangedSubview(rightView)
Here's what my view looks like with 2 items using equalSpacing
And here it is with equalCentering distribution, also a nice look.
I prefer to let the UIStackView handle the spacing. Create a UIStackView with equal spacing and add two 0px wide (0px high if using a vertical stackview) transparent views to the the far sides of your stack view.
You can use constraints and give then same height and width. So when you change the dimension of anyone of the component then all components are changed with same dimension.
I think what you want is to have the same spacing outside of the stack view with the spacing outside.
What I would do is the put stack view inside another view (GRAY VIEW) and set the leading and trailing constraint of the stack view to be equal to the spacing of the stack view.
Spacing of the Stack View
Constraints of the Stack View from its super view (Gray View)

How to evenly space two subviews around the center of UIStackView (iOS 9)?

How to evenly space two subviews around the centre of UIStackView in the vertical axis?
Currently both labels are next to the top and the bottom of the StackView. The stack view is the greyed area on the simulator screen shot.
How to make them evenly spaced?
|
|
[UILabel]
|
|
[UILabel]
|
|
I've tried different configurations, but I couldn't get the desired result.
Here is my current code:
let object = UIStackView()
object.translatesAutoresizingMaskIntoConstraints = false
object.alignment = UIStackViewAlignment.Center
object.axis = .Vertical
object.distribution = UIStackViewDistribution.EqualSpacing
Considering you have similar properties set to UIStackView (as mentioned in the question)
alignment -> Center
axis -> Vertical
distribution -> Equal Spacing
The solution can be achieved by introducing two empty views with height constraint set to 0 to both ends of UIStackView.
Explanation
On Adding two empty views of height 0, we are telling UIStackView that we have 4 views instead of 2.
Now UIStackView will add equal spacing between all the views and hence the output.
You can cross verify it by looking at the height of the UIStackView and the position of the labels.
Hope this helps !!!
In stackView attribute inspecter change spacing value from 0 to your preferred space value..

Autolayout to set six square images

I want to implement autolayout to set six square images that are always be in square even if screen size is changed.
I have tried too many variations but fail to do so.
In attached image i share sample view that autolayout will be applied.
You don't need any view wrappers or other funny business here, you can do it purely within IB or AL constraints between each item. The 'trick' is to think about the relationships between each item and to use both constants and multipliers.
Each square here is 1:1 ratio.
The orange square is 2:1 with to the first yellow square, plus 8 for the padding.
The orange square is pinned to the left, the first yellow square is pinned to the right.
All the other yellow squares are relative width to the first one.
Here's the storyboard file too:
https://www.dropbox.com/s/pk8iwj1beamkxtp/SO_Solution-20151215_2.storyboard?dl=0
Based on a comment, I added one wrapper view to make it easy to apply size classes if you want the entire thing to always be visible. (also makes it easier to drop into another storyboard).
Okay, i'll give you an easy way to achieve this, but this is my implementation, and i'm pretty sure there are a lot of implementations easier.
First, create a empty subview, and add the constraints so the view will always be a square in the top left corner:
Trailing Space to superview >= 0
Trailing Space to superview = 0 #750
Top Space to superview = 0
Left Space to superview = 0
Bottom Space to superview = 0 #750
Bottom Space to superview >= 0
Aspect ratio : 1
Now add in this square the square in the top left corner and the topRightView :
// TopLeftView constraints :
Leading Space to superview = 20
Top Space to superview = 20
Aspect ratio : 1
// TopRightView constraints :
Trailing Space to superview = 20
// Contraints between TopRightView and TopLeftView
Align bottom
Align top
Equal Width
Horizontal spacing = 20
You can now set the ratio between the squares, by setting the multiplier value of the "equal width" constraint. Let's use a 1/3 multiplier.
Let's add the bottomLeftView now. In order to not over constraining our view, we don't need to set a multiplier between the square height and this view height. We know the space on the right of the green square is equal to the space below it, so let's use only spacing and alignement constraints.
// BottomRight constraints:
Bottom Space to superview = 20
// Contraints between BottomLeftView and TopLeftView
Align left
Align right
Vertical spacing = 20
The last view to add is the BottomRightView, and alignment constraints will works well :
// Contraints between BottomRightView and BottomLeftView
Align top
Align bottom
// Contraints between BottomRightView and TopRightView
Align left
Align right
Here we are. Now you just need to add squared subviews in top and bottom of TopRightView, and at left and right of BottomLeftView. You can also change the ratio with a single variable, which would not be possible if you had set a ratio constraint between TopLeftView and BottomLeftView.

Resources