UIStackView - Positioning Image up against edge of sibling - ios

I am trying to create a custom UITableViewCell in iOS that contains a Label and a related image. The image needs to be as close the the trailing edge of the label as possible.
Below is an image of the progress so far.The Red area is the Horizontal UIStackView in which I have placed the UILabel (Green) and the UIImageView (Cyan).
The UILabel as been set to Lines = 0.
I've played around with a number of the UIStackView Distribution and Alignment properties and have in the past made good use of the approach outlined in this article A UIStackView Hack for Stacking Child Views Compactly. In line with the technique in that article I have a third transparent View that has Lower ContentHugggingPriority so takes up most of the room. This almost works but something is causing the label to wrap at that fix point, it looks like it's a 1/3 of the overall width.
Not all rows will show the image.
Does anyone have any other suggestions for how to achieve this layout? I have tried plain old Autolayout (no UIStackView) but that had other issues

This may be what you're after...
The stack view is constrained to Top and Bottom margins, Leading Margin + 12, and Trailing Margin >= 12, with these settings:
Axis: Horizontal
Alignment: Top
Distribution: Fill
Spacing: 0
The image view has Width and Height constraints of 24
The label has no constraints, but has:
Content Compression Resistance Priority
Horizontal: Required (1000)
The result (top set with image view, bottom set without):

Whilst I have marked DonMag's answer as THE answer for this question I am including my own answer as I am creating the Views programatically as that was my final solution.
First up create the UISTackView container for the label and image
this.drugNameStackView = new UIStackView()
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Horizontal,
Alignment = UIStackViewAlignment.Top,
Distribution = UIStackViewDistribution.Fill,
Spacing = 0
};
Then having already created the UILabel add it to the StackView and set Compression Resistance
this.drugNameStackView.AddArrangedSubview(this.drugNameLabel);
this.drugNameLabel.SetContentCompressionResistancePriority(1000.0f, UILayoutConstraintAxis.Horizontal);
The key part of the solution and the main thing I learned from DonMag's answer where these two constraints that I added to the ContentView
this.ContentView.AddConstraints(
new[]
{
NSLayoutConstraint.Create(this.drugNameStackView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.ContentView, NSLayoutAttribute.LeadingMargin, 1.0f, 0),
NSLayoutConstraint.Create(this.ContentView, NSLayoutAttribute.TrailingMargin, NSLayoutRelation.GreaterThanOrEqual, this.drugNameStackView, NSLayoutAttribute.Trailing, 1.0f, 0),
});
Note that it is the ContentView that is the the first item in the constraint and the UISTackView the second for the NSLayoutRelation.GreaterThanOrEqual constraint

Related

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 center vertically labels in a view

I have 4 labels like this in a view:
The view hierarchy like this:
But if one of text in each label is empty, all of other labels should center vertically with the image.
For example: the albumDataLabel.text is empty, then userNameLabel, albumNameLabel, albumLocationLabel should center vertically with the image.
Somethings like this:
So how to do this, please point me to some approaches.
Set height constraint for every label and which label have not text
make it's height zero(from outlet of height constraint by setting constant to 0) at runtime.
Your constraint should be in linear hierarchy like first label's top should be pinned with it's supper view's top and last label's bottom should be pinned with superview's bottom and each and every label's bottom should be pinned with top of below label.
then you should set height constraint for view that contains all labels with constant (>=) of minimum height(least height of your view).
and centered vertically that view with your image view.
you can do this kind of setup!!
Since your 4 Labels are already in a view, you can set the labels' constraints to pin the first Label to the top, last Label the bottom and spacing in between to zero
Then select the view(withLabels) and your ImageView to align their vertical centers
Do not set a height value constraint for your labels nor the view
When one of your labels have an empty string, the height is automatically set to zero and hence 'hidden' so the view(withLabels) will shrink in height. All can be done in the interface builder, no coding necessary, it is just a matter of autolayout.
1) for your userNameLabel:
userNameLabel.leftAnchor.constraintEqualToAnchor(imageView.rightAnchor, constant: 10).active = true
userNameLabel.topAnchor.constraintEqualToAnchor(self.topAnchor, constant: 50).active = true
userNameLabel.widthAnchor.constraintEqualToConstant(220).active = true
userNameLabel.heightAnchor.constraintEqualToConstant(30).active = true
2) for your albumNameLabel:
albumNameLabel.widthAnchor.constraintEqualToConstant(220).active = true
albumNameLabel.heightAnchor.constraintEqualToConstant(30).active = true
albumNameLabel.topAnchor.constraintEqualToAnchor(userNameLabel.bottomAnchor, constant: 5).active = true
albumNameLabel.leftAnchor.constraintEqualToAnchor(imageView.leftAnchor, constant: 10).active = true
3) remember this:
self.addSubview(userNameLabel)
self.addSubview(albumNameLabel)
And go on in this way to all elements in your View.

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

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.

How to add leading padding to view added inside an UIStackView

This is my setup: I have an UIScrollView with leading,top, trialing edge set to 0. Inside this I add an UIStackView with this constraints:
stackView.centerYAnchor.constraintEqualToAnchor(selectedContactsScrollView.centerYAnchor).active = true
stackView.leadingAnchor.constraintEqualToAnchor(selectedContactsScrollView.leadingAnchor).active = true
Inside the stack view I add some views.
My issue is that because of the constraints the first view added to stack view will also have leading edge = 0.
What are the ways that I could add some padding to the first view ? Without adjusting the scroll view constraints.
When isLayoutMarginsRelativeArrangement property is true, the stack view will layout its arranged views relative to its layout margins.
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true
But it affects all arranged views inside to the stack view. If you want this padding for only one arranged view, you need to use nested UIStackView
I have found that constraints don't work inside a Stack View, or they seem somewhat strange.
(Such as if I add leading/trailing constraint to selected on image stackview, that adds leading to collectionview too, but doesn't add trailing; and it be conflict for sure).
To set layout margins for all views inside the stackview, select:
Stack View > Size Inspector > Layout Margins > Fixed
Note: "Fixed" option was formerly called "Explicit", as seen in the screenshots.
Then add your padding:
The solution you have provided doesn't add a padding for your views inside your UIStackView (as you wanted in the question), but it adds a leading for the UIStackView.
A solution could be to add another UIStackView inside your original UIStackView and give the leading to this new UIStackVIew. Then, add your views to this new UIStackView.
Hint, you can do that completely using Interface Builder. In other words, no need to write code for it.
What worked for me is to add to stack view another UIView that's just a spacer (works at least with stackView.distribution = .Fill):
let spacerView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
stackView.addArrangedSubview(spacerView)
stackView.addArrangedSubview(viewThatNeedsSpaceBeforeIt)
stackView.addArrangedSubview(NextView)...
If you only need leading padding, then you can set the stack view's Alignment to "Trailing" and then you will be free to specify unique Leading constraints on each of its contained subviews.
As a bonus, you can also set the stack view's alignment to "Center" and then you can use Leading and/or Trailing constraints to give each item its own padding on both sides.
Set your stackview alignment to "center". After that you can give every subview different leading and trailing.
swift 3:
You just need set offset by:
firstView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 200).isActive = true
Be sure this constraint set after you parentView.addArrangdSubView(firstView)
The solution would be to have a regular view in the stack view to hold whatever views you are wanting to add constraints to, and then you can add constraints for your items that are relative to the views in the stack view. That way, your original views can have leading and trailing constraints within the stack view.
This can be done in the interface builder and programatically.
This question already has good answers,
One suggestion though, use spacing property to set the spacing between the views. For first and last views there can be two options, either set insets as #tolpp suggested or add constraint attaching to parent (stackview) with constant to add padding.
What we did was add transparent components (e.g., UIButton/UIView) as the first and last children of the UIStackView. Then set constrain the width of these invisible children to adjust the padding.
It seems that the solution was pretty simple. Instead of:
stackView.leadingAnchor.constraintEqualToAnchor(selectedContactsScrollView.leadingAnchor).active = true
I just wrote:
stackView.leadingAnchor.constraintEqualToAnchor(selectedContactsScrollView.leadingAnchor, constant: 15).active = true
Just add an empty view at the beginning of the stack view (also constraining its width and/or height):
stackView.insertArrangedSubview(UIView().constrain(width: 0, height: 0), at: 0)
and/or at the end:
stackView.addArrangedSubview(UIView().constrain(width: 0, height: 0))
I created this simple UIView extension to add the constraints:
extension UIView {
func constrain(width: CGFloat, height: CGFloat) -> Self {
NSLayoutConstraint.activate([
widthAnchor.constraint(equalToConstant: width),
heightAnchor.constraint(equalToConstant: height)
])
return self
}
}

UIImageView with Aspect Fill inside custom UITableViewCell using AutoLayout

I've been struggling with fitting an UIImageView which shows images of variable widths and heights with Aspect Fill. The cell height is not adapting to the new height of the UIImageView and persist it's height.
The hierarchy of the views is this
UITableViewCell
UITableViewCell.ContentView
UIImageView
I tried these scenarios in XCode Auto Layout :
set the UIImageView => Height to remove at build time
set the Intrinsic Value of the UIImageView to placeholder
set the Intrinsic Value for each of the UIImageView, ContentView and UITableViewCell to placeholder
With any of these combinations I get this view:
http://i.stack.imgur.com/Cw7hS.png
The blue lines represent the cell borders (boundaries) and the green ones represent the UIImageView border (boundaries). There are four cells in this example, the 1st and the 3rd ones have no images and the 2nd and the 4th ones have the same image (overflowing over the ones which have none).
I cobbled together a solution based on two previous answers:
https://stackoverflow.com/a/26056737/3163338 (See point 1)
https://stackoverflow.com/a/25795758/3163338 (See point 2)
I wanted to keep the AspectRatio of the image regardless of its Height while fixing up the Width according to that of the UIImageView, the container of the image.
The solution comprises of :
Adding a new AspectRatio constraint
let image = UIImage(ContentFromFile: "path/to/image")
let aspect = image.size.width / image.size.height
aspectConstraint = NSLayoutConstraint(item: cardMedia, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: cardMedia, attribute: NSLayoutAttribute.Height, multiplier: aspect, constant: 0.0)
when adding this constraint, xCode will complain about the new "redundant" constraint and attempt to break it, rendering it useless, yet displaying the image exactly like I want. This leads me to the second solution
Lowering the priority of the new constrain to '999' seems to stop xcode from breaking it, and it stopped showing warning message about the new constraint
aspectConstraint?.priority = 999
Not sure why xCode automatically adds UIView-Encapsulated-Layout-Height and UIView-Encapsulated-Layout-Height at build/run time; however, I learned how to respect that and live with it :)
Just leaving the solution here for anyone to check. This is working on iOS 8. I tried with iOS7 but it doesn't work the same as you need to implement tableView:heightForRowAtIndexPath calculating the height of the cell based on all the items contained within it and disable setting up:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0
Suppose you have an UIImage with dimensions 768 x 592 of width and height respectively, the height always remains equals, where the device it's rotated for example in the dimensions above (iPad), the width of the image change to 1024 and the height remains equal.
What you can do to maintain the aspect of the image is scale it in the dimensions you want, for example if you know that the images coming always have the same dimensions, we say for example 1280x740 , you can set the UIImage to .ScaleToFill and calculate in the following way:
(widthOfTheImage / heightOfTheImage) * heightWhereYouWanToSet = widthYouHaveToSet
For example :
(1280 / 740) * 592 = 1024
And it's the width I have to set in my UIImage to maintain the proportions of the image when it's change it's width.
I hope you understand where I try to say to you.

Resources