UITextField autolayout with margins programmatically - ios

I'm new to AutoLayout and would like to display my UITextField at 100% width with a consistent 15px left and right margin, like so:
Typically I would do this using CGRect, setting the width to the containing view's width minus 30px, then offset the left side by 15px:
searchTextField.frame = CGRectMake(15, 0, view.frame.width - 30, 50)
But I'd like to learn AutoLayout for this sort of thing, since it's the way to go these days. I should note that I am doing everything programmatically -- no Storyboards here.
I'd love it if someone could help me out!
Update
Wow! Thank you for all the responses. I believe all of them would achieve what I'm trying to do, but there can only be one :)

Usually I use for this cocoapod that is dealing with constraints, but if you need pure apple solution documentation states:
You have three choices when it comes to programmatically creating
constraints: You can use layout anchors, you can use the
NSLayoutConstraint class, or you can use the Visual Format Language.
Approach with NSLayoutConstraints in your case would be:
NSLayoutConstraint(item: textField, attribute: .Leading, relatedBy: .Equal, toItem: parentView, attribute: .LeadingMargin, multiplier: 1.0, constant: 15.0).active = true
NSLayoutConstraint(item: textField, attribute: .Trailing, relatedBy: .Equal, toItem: parentView, attribute: .TrailingMargin, multiplier: 1.0, constant: -15.0).active = true
NSLayoutConstraint(item: textField, attribute: .Top, relatedBy: .Equal, toItem: parentView, attribute: .TopMargin, multiplier: 1.0, constant: 50.0).active = true
Remember that if you don't have any constraints in the view, they would be added automatically and you'll have to deal with them and conflicts that would be created by adding new constraints on runtime. To avoid this you could either create textField manually and add it to the view or set constraints with low priority in the Interface Builder .

Assuming the parent of the text field is view, do this after view.addSubview(searchTextField):
NSLayoutConstraint.activateConstraints([
searchTextField.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 15),
searchTextField.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor, constant: -15),
])

Use this code:
let topConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Top, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Trailing, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 15)
let leadingConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Leading, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 15)
let heightConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 50)
self.view.addConstraint(topConstraint )
self.view.addConstraint(trailingConstraint)
self.view.addConstraint(leadingConstraint)
self.view.addConstraint(heightConstraint)

Set the constraints in storyboard.
Click on the text field then click on in the bottom left. From there you can choose constraints like that.

To use Auto Layout, you need to define constraints for your text field.Here, I have created four constraints(Leading, Trailing, Top and Height) related to its superview.
func addLabelConstraints(superView:UIView) {
let leading = NSLayoutConstraint(item: searchTextField, attribute: .Leading, relatedBy: .Equal, toItem: superView, attribute: .Leading, multiplier: 1, constant: 15)
superview!.addConstraint(leading)
let trailing = NSLayoutConstraint(item: searchTextField, attribute: .Trailing, relatedBy: .Equal, toItem: superView, attribute: .Trailing, multiplier: 1, constant: 15)
superView.addConstraint(trailing)
let top = NSLayoutConstraint(item: searchTextField, attribute: .Top, relatedBy: .Equal, toItem: superView, attribute: .Top, multiplier: 1, constant: 0)
superView.addConstraint(top)
let height = NSLayoutConstraint(item: searchTextField, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 0, constant: 50)
superView.addConstraint(height)
}

Related

Tab bar - adding a new beam to all controllers

Example
Has anyone got an example of doing something like that?
I want it to be displayed all the time while passing between other controllers. I do not want to create it again in every controller.
Override TabBar controller's view did load, to create your beamView:
let beamViewHeight:CGFloat = 60
let beamView = UIView()
beamView.translatesAutoresizingMaskIntoConstraints = false
beamView.backgroundColor = .black
self.view.addSubview(beamView)
add constraint to align it bottom, top of the tab bar:
let bottom = NSLayoutConstraint(item: beamView, attribute: .bottom, relatedBy: .equal, toItem: self.tabBar, attribute: .top, multiplier: 1, constant: 0)
let leading = NSLayoutConstraint(item: beamView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0)
let trailing = NSLayoutConstraint(item: beamView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0)
let height = NSLayoutConstraint(item: beamView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: beamViewHeight)
view.addConstraints([bottom, leading, trailing, height])
it look's like this:

How do I create a robust ScrollView with both horizontal and vertical paging using autoLayout?

Situation
So I'm trying to make a UIScrollView that is basically a menu navigation bar that a user can navigate by swiping between menu items, where there are pages vertically laid out, and sub-pages horizontally laid out. Mockup:
The way I'm doing this is by creating a UIScrollView whose frame is the size of one UILabel, and I have isPagingEnabled set to true. I then tried adding a UIStackView, with each row indicating a page, and the contents of each row being a sub-page. I set the scrollView.contentSize to be the size of the UIStackView. The problem is that all my label's frames are zeros all through and the UIScrollView doesn't work.
:/
I really wanted to avoid getting help as I felt like I could do this by myself but I've spent two days on this and I've lost all hope.
Code
Here is the code where I add the labels. It's called upon the scrollview's superview's init (because the UIScrollView is in a custom UIView I call crossNavigation View).
private func addScrollViewLabels() {
//Get Max Number of Items in a Single Row
var maxRowCount = -1
for item in items {
if (item.contents.count > maxRowCount) {maxRowCount = item.contents.count}
}
self.rowsStackView.axis = .vertical
self.rowsStackView.distribution = .fillEqually
self.rowsStackView.alignment = .fill
self.rowsStackView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.addSubview(rowsStackView)
for i in 0 ..< items.count {
let row = items[i].contents
let rowView = UIView()
self.rowsStackView.addArrangedSubview(rowView)
var rowLabels : [UILabel] = []
//First Label
rowLabels.append(UILabel())
rowView.addSubview(rowLabels[0])
NSLayoutConstraint(item: rowLabels[0], attribute: .leading, relatedBy: .equal, toItem: rowView, attribute: .leading, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: rowLabels[0], attribute: .top, relatedBy: .equal, toItem: rowView, attribute: .top, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: rowLabels[0], attribute: .bottom, relatedBy: .equal, toItem: rowView, attribute: .bottom, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: rowLabels[0], attribute: .width, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: 0.55, constant: 0.0).isActive = true
//Middle Labels
for j in 1 ..< row.count {
rowLabels.append(UILabel())
rowView.addSubview(rowLabels[j])
//Stick it to it's left
NSLayoutConstraint(item: rowLabels[j], attribute: .leading, relatedBy: .equal, toItem: rowLabels[j-1], attribute: .trailing, multiplier: 1.0, constant: 0.0).isActive = true
//Stick top to rowView
NSLayoutConstraint(item: rowLabels[j], attribute: .top, relatedBy: .equal, toItem: rowView, attribute: .top, multiplier: 1.0, constant: 0.0).isActive = true
//Row Height is equal to rowView's Height
NSLayoutConstraint(item: rowLabels[j], attribute: .height, relatedBy: .equal, toItem: rowView, attribute: .height, multiplier: 1.0, constant: 0.0).isActive = true
//rowLabels[j].width = containerView.width * 0.55 (so other labels can peek around it)
NSLayoutConstraint(item: rowLabels[j], attribute: .width, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: 0.55, constant: 0.0).isActive = true
}
//lastLabel.trailing = rowView.trailing
NSLayoutConstraint(item: rowLabels[row.count-1], attribute: .trailing, relatedBy: .equal, toItem: rowView, attribute: .trailing, multiplier: 1.0, constant: 0.0).isActive = true
}
//Constraints for stack view:
//rowsStackView.height = scrollView.height * items.count
NSLayoutConstraint(item: rowsStackView, attribute: .height, relatedBy: .equal, toItem: scrollView, attribute: .height, multiplier: CGFloat(self.items.count), constant: 0.0).isActive = true
//rowsStackView.height = scrollView.height * items.count
NSLayoutConstraint(item: rowsStackView, attribute: .leading, relatedBy: .equal, toItem: containerView, attribute: .leading, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: rowsStackView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: rowsStackView, attribute: .width, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: 1.0, constant: 0.0).isActive = true
self.scrollView.contentSize = rowsStackView.frame.size
}
Take a look at this demo project I made based off your mock up.
It sets up the framework for everything you want to do.
Examine the view hierarchy and you'll understand where to configure the layout to make it exactly how you want.
The last step is to tweak the paging so you get the snap behavior you desire. There are many ways to do this but it is a bit involved.
Good Luck!

Programatic Autolayout UI Elements

I am trying to autolayout 3 UI elements in the order which I present in the image. A UITextfield, UIDatePicker and a UIButton in a UIView.
I am avoiding to use storyboard as I want to get a better understanding of programmatic constraints and eventually use animations for them.
So far I have got this with some constraints I have tried:
and here is the code for the one I am working on:
override func viewDidLoad() {
super.viewDidLoad()
picker.translatesAutoresizingMaskIntoConstraints = false
picker.backgroundColor = UIColor.red
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
button.setTitle("Button", for: .normal)
self.view.addSubview(picker)
self.view.addSubview(button)
let PickercenterX = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let PickercenterBottom = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.button, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: -30)
let Pickerheight = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 150)
let Pickerwidth = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.width, relatedBy: .equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: -5)
// Centers it on the x axis. Pushes it it right if co constant has a value > 0
let centerX = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let centerBottom = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: -15)
let height = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 50)
let width = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.width, relatedBy: .equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: -15)
self.view.addConstraints([centerX, centerBottom, height, width, PickercenterX, PickercenterBottom, Pickerheight, Pickerwidth])
}
I am trying to work the button and date picker first before moving onto the textfield. How can I achieve this programmatically ?
Here lies the problem:-
You were setting the picker bottom to be equal to the button bottom with constant -30, although I know what were you trying to do, you were trying to give vertical space between picker and button. So it should be linked like, picker's bottom equal to button's top with constant -30.
Moreover you are missing out on activating the constraints by not adding isActive in the end.
Another way to activate all constraints at once is by using NSLayoutConstraint.activate() method
let PickercenterBottom = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.button, attribute: NSLayoutAttribute.top, multiplier: 1, constant: -30).isActive = true

I need top constraint of unknow element

I have an app which is downloading image from server by clicking the button. After image have downloaded i create a new imageView and add it to the my contentView(UIView). I need to create the constraints - every new imageview need top constraint from previous one
func addNewImageToTheScrollView(img: UIImage?) {
if let imageResponse = img {
let imageView = UIImageView(image: imageResponse.crop(rect: CGRect(x: 0, y: imageResponse.size.height/2, width: self.contentView.frame.width, height: 200)))
self.contentView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self.contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
self.contentView.addConstraints([x, y])
imageView.addConstraints([width, height])
}
}
If i comment the constraint code, it will be work fine unless every new imageView will be on the same place, on the top of the View. Now whit this constraint code i have such code issue after downloading
2017-07-02 14:50:01.018 ImageFromServerTest[11516:1080948] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSLayoutConstraint for >: A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs.'
Whenever you are working with scrollViews, there are 2 thumb rules for it:-
Give the scrollView leading, trailing, bottom, and top constraint with respect to the superview, that is self.view
#IbOutlet weak var scrollView: UIScrollView!
let leading = NSLayoutConstraint(item: scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leadingMargin, multiplier: 1.0, constant: 0)
let trailing = NSLayoutConstraint(item: scrollView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailingMargin, multiplier: 1.0, constant: 0)
let top = NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0)
let bottom = NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0)
Add constraints to the contentView with respect to the scrollView
#IbOutlet weak var contentView: UIView!
let leading = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: scrollView, attribute: .leading, multiplier: 1.0, constant: 0)
let trailing = NSLayoutConstraint(item: contentView, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1.0, constant: 0)
let top = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1.0, constant: 0)
//increase the constant according to how much long you need the scrollview to be
let bottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 0)
Now add your subviews constraints (labels, images) with respect to the contentView
For example- You received your first image, so we will maintain an array of UIImageViews outside your function.
var imageViews = [UIImageViews]() //declared outside the function
//received an image here
var imageView = UIImageView() // define the frame according to yourself using frame init method
imageView.image = image
if imageViews.isEmpty { // first image view
//add constraints to first image view
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
}
else { //second, third, fourth image view... so on
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: imageViews[imageViews.count - 1], attribute: .bottom, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
}
imageViews.append(imageView)
}
Hope you got an idea now, how to proceed with this problem. If having more than 4 or 5 imageviews, you'll probably want to check the count of the array and increase the contentView of the scrollView accordingly. you can do so by using
self.scrollView.contentSize = CGSize(width, height)
I believe you have a problem here:
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)
"toItem" parameter should have some value. Also first parameter should be imageView.topAnchor. Probably it should look something like this:
let y = NSLayoutConstraint(item: imageView.topAnchor, attribute: .top, relatedBy: .equal, toItem: self.contentView.topAnchor, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)

Autolayout - UIView does not resize correctly to subview's size

I'm trying to programmatically create a UIView with a UILabel as a subview using autolayout.
Constraints
I used the following constraints on view:
CenterX of view to fastAttacksContainerView (superview).
Top constraint of constant 8 to superview.
Then created a label which is a subview of view and added constraints of constant 8 for Top, Bottom, Left, and Right to view.
Problem
The view only resizes to the frame of the label and does not account for the 4 constraints of constant 8 on all 4 sides. Which causes the label to be displayed partially outside the view.
let view = UIView()
view.backgroundColor = pokemon.secondaryColor
let label = UILabel()
fastAttacksContainerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = fa
let gest = UITapGestureRecognizer(target: self, action: #selector(self.selectFastAttack))
view.addGestureRecognizer(gest)
fastAttackButtons.append(view)
fastAttackLables.append(label)
let top = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .Top, multiplier: 1, constant: 8)
let centerX = NSLayoutConstraint(item: view, attribute: .CenterX, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .CenterX, multiplier: 1, constant: 0)
let labLeft = NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 8)
view.addConstraints([labLeft, labTop, labRigth, labBottom])
fastAttacksContainerView.addConstraints([top, centerX])
Output
view has ambiguous height.
You should add constraint for height of view or distance from it to bottom of fastAttacksContainerView.
I managed to fix this problem by making a similar setup on storyboard and copying the constraints in.
Things that I changed:
Used Leading and Trailing instead of Left and Right layout attributes.
For the labLeft and labRight constraints, I interchanged item and toItem. (This seems like a bug to me. Can anyone verify, please?)
Code change:
let labLeft = NSLayoutConstraint(item: label, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: label, attribute: .Trailing, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: label, attribute: .Bottom, multiplier: 1, constant: 8)

Resources