Add layoutMargins to one element in a UIStackView - swift

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.

Related

How to get UIView height and width in Swift? (problem: returning zero)

Sorry in advance for the probably silly question - I'm a beginner
I'm generating a new UIView in viewDidLoad, providing some constraints to anchor it over the main view. When it comes to understand the size of the new view, I always get zero.
Here is a simplified version of my code which is not working:
override func viewDidLoad() {
super.viewDidLoad()
let myView = UIView(frame: .zero)
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
myView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -200).isActive = true
myView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
myView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
print(myView.bounds.width)
}
The width (but also height) is returning 0.
This happens both with .bounds and .frame.
Do you have any hints?
Thanks!
First question I will ask, does the UIView display on the screen?
Second, from the knowledge of view controller life cycle, the views are laid out in viewDidLayout Subviews.
So, you want to called myView.bounds.width in by overriding the viewDidLayout Subviews or viewDidAppear, when the views have finally appeared in the screens
Thanks for making the question more clear.
I will suggest you either reloadData on the second collection view on viewDidLayout subviews. That way, the two gets layout as it is now, then the second collection view gets layout for the second time after the first collection view is laid.
Another approach will be for you to use relative constraints. I.e. Making weight and height constraints of the items in the second collection view relative to the items in the first collection view. This way, everything else is done automatically for you at runtime. Even if the size of the items in first collection view change later which might affect items of the second collection view, it will be taken care off. (For accurate result if you can get it to work)
I will appreciate if you can share more of the code or screen shots of these two collections views. I could provide a better approach to creating them

How to programmatically set constraints when View sizes are different?

Using Xcode 10, iOS 11.4+, Swift 4
I have a series of UIViewControllers that I am navigating through,
each of which is being placed into a ContainerView.
Based on which VC is pushed, I am hiding/showing the Top or Bottom views (shown in gray) in the Main Controller while the ContainerView is always in the middle (light blue).
What I would like to do is set the constraints so that the ContainerView is appropriately sized when the Top or Bottom views are shown/hidden.
For example, when the "Main Menu" is shown, the ContainerView should fill the entire Main Container view (Top and Bottom views will be hidden)
When "Item1" is shown, the Top view will be shown and the Bottom view hidden. Therefore, ContainerView should fill the Main Container view left, right, and bottom, but the ContainerView Top should be constrained to the Top view bottom.
When "Item5" is shown, the Top and Bottom views will also be shown.
Therefore, ContainerView should fill Main Container view left, right, and be constrained to the Top view bottom, and the Bottom view top (as shown in the Main Container)
I've tried using code like this to fill the entire Main view:
NSLayoutConstraint.activate([
ContainerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0),
ContainerView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0),
ContainerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
ContainerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
])
However, the ContainerView never changes, and Xcode gives me a lot of warnings like:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want.
Here's another screenshot from the Storyboard:
Here's a link to download my sample project:
https://gitlab.com/whoit/containerviews
How can I correctly modify the constraints to accomplish this?
As I've mentioned in the comment, you should have used UIStackView for your top / bottom views visibility controlling.
You will need a UIStackView with following attributes:
Axis: Vertical
Alignment: Fill
Distribution: Fill
Spacing: As per your need
The stack view will contain the Top View, Container View and Bottom View respectively as its sub views.
You need to pin all the sides (leading, trailing, top & bottom) of this stack view to its superview (view controller's view). And as you need some height constraints for your top & bottom view, you give them as your need.
So basically your stack view is now capable of resizing its sub views when you hide any of them. You just need to perform:
theSubViewNeedsTobeHidden.isHidden = true
I've made a working demo for you that can be found here.
Issues
1) You're adding new constraints all the time, as this lines create a new constraints, each time they're getting called:
ContainerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0)
2) In the beginning, the MainContainerView is not constraint at all
Solution
I would suggest following:
add those four constraints in the storyboard instead
create IBOutlets for the height-constraints of your top and bottom views.
set the constants of those to 0 (when hiding them)
(optional) for sure you can still fade them in/out by setting their alpha as well, and then add the height constant to the completion block.
Note:
Hidden views (alpha = 0 or isHidden = true) are still taken into account for layouting. That means, your constraints are still valid, when the views are hidden. But to make them look like hidden for the layouting as well, you'll then have to set theirs height constants to 0.
Code
#objc func hideControlViews(_ notification: Notification){
let userInfo = notification.userInfo! as! [String : Bool]
//print("Got top view notification: \(String(describing: userInfo["hide"]))")
if (userInfo["hidetop"]!) {
self.topViewHeightConstraint.constant = 0
} else {
self.topViewHeightConstraint.constant = 64
}
if (userInfo["hidebottom"]!) {
self.bottomViewHeightConstraint.constant = 0
} else {
self.bottomViewHeightConstraint.constant = 108
}
self.view.layoutIfNeeded()
}

Trouble with UIView Constraints

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.

Setting my button's position programmatically in swift

I've been using auto layout so far, so I'm not sure how this works.
I have a simple button I want to be close to my down right corner.
How can its position programmatically in swift?
Here you go:
let button = UIButton()
button.setTitle("Button", forState: .Normal)
button.backgroundColor = UIColor.blackColor()
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor, constant: -20).active = true
button.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor, constant: -20).active = true
Setting the translatesAutoresizingMaskIntoConstraints to false is necessary. By default, the property is set to true for any view you programmatically create.
In this case you need only 2 constraints (bottomAnchor and trailingAnchor). Since the button has the intrinsicContentSize (the natural size based on the title and stuff), you don't have to add constraints for the height/width.
Also you might want to use the layoutMarginsGuide to pin the button to the edges of the superview. This way you don't need to specify the constants:
superView.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
let margins = superView.layoutMarginsGuide
button.bottomAnchor.constraintEqualToAnchor(margins.bottomAnchor).active = true
button.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor).active = true
Note that if the view is a view controller’s root view, then the system sets and manages the margins. The top and bottom margins are set to 0 points. The side margins vary depending on the current size class, but can be either 16 or 20 points. You cannot change these margins.
You can use autolayout programmatically.
SnapKit is a nice swift library to make the autolayour code more easy to use and readable.
Otherwise you can always use it the normal way. These are two nice tutorials on it:
1
2

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 :)