Trouble with UIView Constraints - swift

To start with, here's a mockup of the layout I'm trying to accomplish in Swift.
And here's what I have so far,
So here's the problem. Notice that in the second image the green UIView overflows exceeding the TableViewCell height.
In my Main TableViewController class I've defined each cell to be 120 pixels in height, and the green UIView 10 pixels short of the cell height with the 10 pixels on top as a separator between subsequent cells.
Cell height definition:
var itemHeight = [CGFloat](count: 2, repeatedValue: 120.0)
UIView constraints:
foregroundView.topAnchor.constraintEqualToAnchor(foregroundView.superview?.topAnchor, constant: 10).active = true
foregroundView.leftAnchor.constraintEqualToAnchor(foregroundView.superview?.leftAnchor, constant: 20).active = true
foregroundView.widthAnchor.constraintEqualToAnchor(foregroundView.superview?.widthAnchor, constant: -40).active = true
foregroundView.heightAnchor.constraintEqualToConstant(110).active = true
Any ideas on what might be causing the UIView overflow?
Thanks in advance!
Please let me know if you'd like to see the code in context :)

Remove two lines of code from your project:
foregroundView.layoutIfNeeded()
And
containerView.layoutIfNeeded()
They are forcing an untimely laying out of content that is causing your problem.

Related

UIStackView - arranged views are overlapping

I've been struggling for the past hours to add custom views to a UIStackView. The StackView is placed inside a UIScrollView, with constraints set to each margin of the ScrollView. Everything is design in a Storyboard.
Then in code I have the following for loop which should add my custom views to the stack:
for name in names {
let initialChildProfile = ChildProfileView.loadFromNibNamed(nibNamed: "ChildProfileView") as! ChildProfileView
initialChildProfile.frame = CGRect(x: 0, y: 0, width: childrenStackOutlet.frame.size.width, height: initialChildProfile.frame.size.height)
initialChildProfile.isUserInteractionEnabled = true
childrenStackOutlet.addArrangedSubview(initialChildProfile)
}
I've done this so many times before and everything went fine, but this time the custom views are overlapping each other. Only if I set the spacing to something larger than 0, I can actually see that is more than 1 view.
I tried setting the "translateAutoresizingMaskIntoConstraints" to false, I tried setting hardcoded values for the frame of the custom views, different constraints to the stack, even removed it from the scrollview. But nothing works.
PS I tried the few solutions I've seen online. Still nothing.
Thank you.
the problem is autolayout
for name in names {
let initialChildProfile = ChildProfileView.loadFromNibNamed(nibNamed: "ChildProfileView") as! ChildProfileView
initialChildProfile.translatesAutoresizingMaskIntoConstraints = false
initialChildProfile.widthAnchor.constraint(equalToConstant: childrenStackOutlet.frame.size.width).isActive = true
//initialChildProfile.heightAnchor.constraint(equalToConstant: self.view.frame.width - 50).isActive = true
initialChildProfile.isUserInteractionEnabled = true
childrenStackOutlet.addArrangedSubview(initialChildProfile)
}

Add layoutMargins to one element in a UIStackView

I would like to create a vertical stackview with 3 elements in it.
I want a bit more space only between the 2nd and the last element. So I thought about adding to the last element :
mylastelement.layoutMargins = UIEdgeInsets(top:30, left:0,bottom:0, right:0)
But the layoutmargins are not applied in my stackview. Is there any easy way to achieve that (Id like to avoid to modify the last element inner height).
EDIT : I just tried to increase 2nd element height (+50) within its frame by doing :
my2ndElementLabel.sizeToFit()
my2ndElementLabel.frame = CGRect(x:my2ndElementLabel.frame.origin.x,y:lmy2ndElementLabel.frame.origin.y,
width:my2ndElementLabel.frame.width, height:my2ndElementLabel.frame.height + 50)
but it has no effect.
EDIT2 : I tried to add a random view to my UIStackView, but the the view is just ignored ! May have missed something in understanding how UIKit work ?... :
let v = UIView(frame:CGRect(x:0,y:0,width:100,height:400))
v.backgroundColor = .red
myStackView.addArrangedSubview(v)
//...
Here is an extension I made that helps to achieve fast such margins :
extension UIStackView {
func addArrangedSubview(_ v:UIView, withMargin m:UIEdgeInsets )
{
let containerForMargin = UIView()
containerForMargin.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: containerForMargin.topAnchor, constant:m.top ),
v.bottomAnchor.constraint(equalTo: containerForMargin.bottomAnchor, constant: m.bottom ),
v.leftAnchor.constraint(equalTo: containerForMargin.leftAnchor, constant: m.left),
v.rightAnchor.constraint(equalTo: containerForMargin.rightAnchor, constant: m.right)
])
addArrangedSubview(containerForMargin)
}
}
What you could do is set a custom spacing between the second and third element.
myStackView.setCustomSpacing(30.0, after: my2ndElementLabel)
In the same general vein, you can constrain the top (or bottom) anchor of your view relative to the corresponding edge of any view in which it's embedded. What's ugly being somewhat a matter of taste, I find autolayout constraints easy to use and easy to reason about.
A simple example from Mac OS rather than iOS:
let button = ControlFactory.labeledButton("Filter")
addSubview(button)
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20).isActive = true
button.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
This particular code lives in the view initializer, and positions a button in the middle of a view, 20 points up from the bottom.
I found myself : It looks like UIStackView doesn't work at all with old sizing system (with .frame). It seems you have to constraint height and width, and StackView will constraint left/top/right/bottom position for you when you add the arrangedSubview.
My second view was a label : I wanted a margin of 40, under the text. So i first computed the label height into its .frame property, and constraint the height at frame.height + 40(= my margin)
labelDesc.sizeToFit()
labelDesc.heightAnchor.constraint(equalToConstant:40).isActive = true
I find my own solution utterly ugly though. I'm sure UIKit provide a better way to achieve such a simple goal, without having to make these kind of DIY solutions. So please if you're used to work with UIKit, tell me if there is any better solution.
Consider adding a "margin" by inserting a correctly-sized UIView within the Stack View as needed.
If you need a 40px margin between 2 specific elements... add a UIView with a height constraint of 40px. Assign a clearColor background to make it invisible.
You can add IBOutlets to this view and hide it as you would any other item in the Stack View.

How can I avoid specifying potentially conflicting constraints

I am trying to create all of my views in my UICollectionViewCell programatically. The views currently lay out correctly.
I have a vertical stackView that contains two UILabels and a UIImageView.
The views do not have a fixed content size and are all variable.
If you see in my code, I am setting the leading and trailing constraints of my UIStackView with a constant of 8.0 from the safeAreaLayoutGuide. Because of this, I have to set the widthAnchor to be -16.0 (otherwise the cell does not lay out correctly). Is there a way I can avoid specifying this in two places? For example, if I set the leading to be 14.0 points from the leading, then I will have to make sure I remember to change my width to be -20.0, otherwise the contents will spill over.
override func updateConstraints() {
/// Check if views already have constraints
if !viewsHaveConstraints {
viewsHaveConstraints = true
/*
Here I set the hugging and compression priority for the 3 views in the UIStackView
*/
let margins = safeAreaLayoutGuide
/* Set vstackView constraints */
/// vStackView width anchor equal to width of margin less constant
vStackView.widthAnchor.constraint(equalTo: margins.widthAnchor, constant: -16.0).isActive = true
/// vStackView leading anchor constraint
vStackView.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 8.0).isActive = true
/// vStackView trailing anchor constraint
vStackView.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: 8.0).isActive = true
vStackView.heightAnchor.constraint(equalTo: margins.heightAnchor).isActive = true
}
super.updateConstraints()
}

Adjust Auto-Layout inside UITableViewCell

So I am creating a forum. When a post includes an image I would like to keep the aspect ratio of the downloaded image while making the image's width constant at 75% of the cell's width.
I created an outlet for the imageHeightConstraint in my custom cell class and tried the code below in the cellForRowAtIndexPath method.
let newHeight = (image!.size.height/image!.size.width)*cell.uploadedImageView.frame.width
cell.imageHeightConstraint.constant = newHeight//Calculate the new height
The code above doesn't adjust the image size at all.
Currently I have the constraint for image width to be 75% of cell's width and height equal 200.
I also already have all the code to adjust the tableviewcell automatically depending on content and that is working fine.
Thanks for any help.
Try
if let image = image {
cell.image.heightAnchor.constraint(equalTo:cell.image.widthAnchor, multiplier: image.size.height/image.size.width).isActive = true
}
If you want to also limit the height to 200, then you can also add
cell.image.heightAnchor.constraint(lessThanOrEqualToConstant:200).isActive=true

Making a UIButton a % of the screen size

I noticed certain button sizes look great on one the iPhone 5 simulator but do not look as good on the iPhone 6 simulator and this is because the heights or constraints that I place on the UIButtons end up leaving a lot of blank space down the bottom of my App Screen.
I would like to have a button that is 40% of the screen size regardless of what device I am simulating on.
Any ideas on how to make the size of the button stay 40% of the screen size regardless of the device?
Ctrl drag from button to superview and select Equal Widths
Open Size Inspector edit Equal Widths constraint and set multiplier to 0.4.
And you will see something like this:
Add missing constraints and update frames.
You can't set a constraint to be 40% of the screen height. However, if the button always has a 20px leading and trailing to the superview you could use that width and set an aspect ratio height.
Another way could be to use UIScreen.mainScreen().bounds.size.height * 0.4 for your button height.
A third way is to use the button's superview to set the height. Assuming that your button is a direct child of a UIViewController's view: view.bounds.size.height * 0.4.
There's probably a bunch of other ways to do this as well but none of them will involve setting the height to an actual percentage as far as I'm aware.
This function gives you the bounds of the screen
var bounds = UIScreen.mainScreen().bounds
And then you can set the button width multipliying 0.4 x bounds.size.width
Regards
Swift 4.2
In code it's really easy:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height * 0.4).isActive = true
}
or use this instead of current heightAnchor:
button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.4).isActive = true
hope this help :)