Adding constraint to UILabel Programatically with NSLayout - swift

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.

Related

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 translatesAutoResizingMaskIntoConstraints false causes ambiguous layout issue on UITextFields rightview

I don't know whether making every objects translatesAutoresizingMaskIntoConstraints flag false is bad habit but here is my question related with this;
I have UITextField object and I am setting its rightView as UIImageView but when UIImageView's translatesAutoresizingMaskIntoConstraints property sets to false in debug view hierarchy ambiguous layout warning appears,
apple's documentation says under translatesAutoresizingMaskIntoConstraints topic:
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.
but there is method of UITextField
open func rightViewRect(forBounds bounds: CGRect) -> CGRect
that
Returns the drawing location of the receiver’s right overlay view.
Additionally when I am trying to give constraints between rightView and UITextField, it says they are not under same hierarchy.
So why making that flag as false causes ambiguous layout?
let imageView = UIImageView(image: UIImage(named: "infoIcon"))
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false //this line causes ambiguous layout
imageView.widthAnchor.constraint(equalToConstant: 15).isActive = true
textField.rightViewMode = .unlessEditing
textField.rightView = imageView
I was having a similar issue but in my case I forgot the add translatesAutoresizingMaskIntoConstraints as false.
The things your wrote about translatesAutoresizingMaskIntoConstraints is correct. And usually it is not great couple when you used with set a frame or bounds.
Anyway I tried your code and I have some idea what might be effect;
First check your UITextField constraints. If your constraint is not fit well then it could be break after adding rightView.
Secondly init your imageView with frame like below;
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: "logo") imageView.contentMode = .scaleAspectFit
imageView.widthAnchor.constraint(equalToConstant: 15).isActive = true
textField.rightViewMode = .always
textField.rightView = imageView
And finally I think it would be better if you do not use imageView.widthAnchor.constraint(equalToConstant: 15).isActive = true
Use an image with best optimal resolution for your textfield and rest would be fine.

Setting constraints on UITableView inside UIView

I have a UIViewController that has some base controls and in the center has a UIView (as container of swappable ui controls). I swap out the UI controls in this center UIView (container) depending on what the user is trying to do. All of the UI controls that go in to the UIView container are defined programmatically and I use programatic constraints to place them inside the UIView container.
This works fine for all of the sets of UI controls I have done so far. Now I am adding a set of controls to the UIView container that includes a UITableView
I cant figure out how to get the TableView to show up inside the UIView programatically. I can define say a button and label and run the app and see the container with the button and the label. If I add the UITableView as below then the container just does not show up at all.
// tableView
tableView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: inputsContainerView.bottomAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/2).isActive = true
prior to the above code I have already added all of the needed controls to the container subview ...
// Add controls to the view
inputsContainerView.addSubview(listTextView)
inputsContainerView.addSubview(listImageButton)
inputsContainerView.addSubview(listImageClear)
inputsContainerView.addSubview(tableView)
If I leave off the tableview then the container shows up with the other three fields. If I add the tableview then the container and all the other three controls are gone.
How do I add the tableView to the UIView and have it show up?
Here is how I defined the UITable view
let tableView: UITableView = {
let tv = UITableView()
return tv
}()
as a compare, when I define others controls like this they show up fine after adding to the subview and setting the constraints programatically
e.g.
// DEFINE
let listTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = ""
textView.textColor = defaultTextColor
textView.font = subtitleFont
textView.layer.borderColor = UIColor.black.cgColor
textView.layer.borderWidth = 1
textView.textAlignment = NSTextAlignment.left
return textView
}()
then later
// Place - with constraints
// listTextView
listTextView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: padFromLeft).isActive = true
listTextView.topAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: 10).isActive = true
listTextView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 9/16).isActive = true
listTextView.heightAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 5/16).isActive = true
Just added my comment as detailed answer, so others can see the solution faster and benefit from it.
So taken from the apple documentation, to set your own constraints programmatically, you need to set translatesAutoresizingMaskIntoConstraints to false:
Note that the autoresizing mask constraints fully specify the view’s size and position; therefore, you cannot add additional constraints to modify this size or position without introducing conflicts. 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.
So in your case you miss to set it for your table view, when you define it. Just add this line to it:
tv.translatesAutoresizingMaskIntoConstraints = false

Updating the Height Constraint of a View Inside of a UIStackView

Currently, I set the height and width constraints of one of my views that I later add to the stack view as follows: (For your information, productsTable is a UITableView.)
productsTable.heightAnchor.constraintEqualToConstant(tableHeight).active = true
productsTable.widthAnchor.constraintEqualToConstant(stackWidth).active = true
stackView.insertArrangedSubview(productsTable, atIndex: productsTableIndex)
Later, in ViewWillAppear, I want to change the productsTable height as follows:
productsTable.heightAnchor.constraintEqualToConstant(newHeight).active = true.
Despite this, the table view remains the same size after (changing/updating) the constraint in ViewWillAppear. Is there anything I am doing wrong or can do differently to achieve the desired effect?
You need to keep a reference to the height constraint that you create the first time.
let heightConstraint = productsTable.heightAnchor.constraintEqualToConstant(tableHeight)
heightConstraint.active = true
Later, in viewWillAppear() you'll be able to directly set the constant attribute of this constraint to newHeight.
heightConstraint.constant = newHeight

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