Setting my button's position programmatically in swift - 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

Related

Set NSButton size in NSStackView

I programmatically create buttons using this code
let button: NSButton = NSButton()
button.action = #selector(ViewController.eject)
button.bezelStyle = .texturedSquare
buttons.append(button)
stackView.addView(buttons[i], in: .leading)
The problem is that those buttons appear like that
I would like to set the (x , y) position and the size of those buttons. For example, I would like to set their width to fill the whole space.
I tried multiple solutions:
button.frame = CGRect(x: 0.0,y: 0.0,width: 100.0,height: 100.0)
and play with the arguments, but still their size don't change.
I then tried to add them as SubViews to stackView, but obviously they overlap with each other.
Any suggestion?
To fix those buttons you first have to modify the StackView element in the storyboard.
Go to the menu of the storyboard and modify it like that
With the "fill" distribution you let the buttons to fill the entire width of the stackview.
With "spacing" you define how much space there has to be between each button.
Then you go there
To give some space to the buttons just play with the Edge Insets values.
Then set the horizontal Hugging Propriety to 250.
The last thing you have to do is to add this code in your ViewController file.
button.setContentHuggingPriority(NSLayoutConstraint.Priority.init(1), for: .horizontal)
button.translatesAutoresizingMaskIntoConstraints = false
In this way you'll the the horizontal Hugging Propriety of the button to 1.
Here's the buttons I created

How do I make a Swift UIStackView size dynamically based on content?

I have added the following UIStackView to my ViewController. As the views change the values of textOneLabel and textTwoLabel change.
With the following code the initial StackView is centered and the portions filled proportionally. However with subsequent text combinations the bounds of the StackView don't change, leaving the content off center. How can I change the StackView properties so it will adapt to the content and always stay centered?
headerStackView.axis = .horizontal
headerStackView.distribution = .fillProportionally
headerStackView.spacing = 8
headerStackView.layer.borderWidth = 1
headerStackView.layer.borderColor = UIColor.red.cgColor
headerStackView.translatesAutoresizingMaskIntoConstraints = false
headerStackView.topAnchor.constraint(equalTo: timerHeader.topAnchor, constant: 4).isActive = true
headerStackView.centerXAnchor.constraint(equalTo: timerHeader.centerXAnchor).isActive = true
headerStackView.addArrangedSubview(textOneLabel)
headerStackView.addArrangedSubview(textTwoLabel)
First, forget you ever heard of .fillProportionally...
it doesn't do what you think it does
you'll encounter very unexpected layout issues if your stack view has spacing greater than Zero
if your stack view has no width (neither width anchor nor leading/trailing anchors), .fillProportionally doesn't do anything
So, change your .distribution to .fill.
Add these lines to control what auto-layout does with your labels:
textOneLabel.setContentHuggingPriority(.required, for: .horizontal)
textOneLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
textTwoLabel.setContentHuggingPriority(.required, for: .horizontal)
textTwoLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
Now, your stackView FRAME will remain centered.

Adding constraint to UILabel Programatically with NSLayout

I am trying to add a UILabel to my ViewController and use NSLayout to constrain it. However the UILabel doesn't even appear in my view when I try to use NSLayout. Anyone know a solution?
excersice1label = UILabel()
excersice1label.textAlignment = .center
excersice1label.text = "Excercise 1"
excersice1label.font = UIFont.boldSystemFont(ofSize: 20)
view.addSubview(excersice1label)
NSLayoutConstraint.activate([
excersice1label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
excersice1label.centerYAnchor.constraint(equalTo: view.centerYAnchor ,constant: -110),
excersice1label.widthAnchor.constraint(equalToConstant: view.frame.width - 64)
])
You need to add this line:
exercise1label.translatesAutoresizingMaskIntoConstraints = false
According to Apple documentation:
If this property’s value is true, the system creates a set of
constraints that duplicate the behavior specified by the view’s
autoresizing mask. If you want to use Auto Layout to dynamically
calculate the size and position of your view, you must set this
property to false, and then provide a non ambiguous, nonconflicting
set of constraints for the view.

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.

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