Programmatically creating layout, using stack view and constraints not working - ios

I'm trying to create this layout programmatically in Swift.
https://codepen.io/anon/pen/NXRQbJ
The first rectangle is small. And the other 2 rectangles are the same size but bigger than the first rectangle.
Here is the entire View Controller. I am not using storyboard. All my code is in this view controller.
import UIKit
class ViewController: UIViewController {
lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [smallRectangleView, bigRectangleView, bigRectangleView2])
stackView.alignment = .fill
stackView.distribution = .fillProportionally
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
var smallRectangleView: UIView = {
let view = UIView()
view.backgroundColor = .orange
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var bigRectangleView: UIView = {
let view = UIView()
view.backgroundColor = .green
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var bigRectangleView2: UIView = {
let view = UIView()
view.backgroundColor = .purple
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupLayout()
print(stackView.frame)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupLayout() {
// Stack View
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// Small Recntangle View
let heightConstraint1 = NSLayoutConstraint(item: smallRectangleView, attribute: .height, relatedBy: .equal, toItem: bigRectangleView, attribute: .height, multiplier: 4.0, constant: 0.0)
// Big Rectangle View
let heightConstraint2 = NSLayoutConstraint(item: bigRectangleView, attribute: .height, relatedBy: .equal, toItem: bigRectangleView2, attribute: .height, multiplier: 0.0, constant: 0.0)
view.addConstraints([heightConstraint1, heightConstraint2])
}
}
heightConstraint1 and heightConstraint2 is causing errors. I don't know why.
// Small Recntangle View
let heightConstraint1 = NSLayoutConstraint(item: smallRectangleView, attribute: .height, relatedBy: .equal, toItem: bigRectangleView, attribute: .height, multiplier: 4.0, constant: 0.0)
// Big Rectangle View
let heightConstraint2 = NSLayoutConstraint(item: bigRectangleView, attribute: .height, relatedBy: .equal, toItem: bigRectangleView2, attribute: .height, multiplier: 0.0, constant: 0.0)
view.addConstraints([heightConstraint1, heightConstraint2])
When I run this app on the simulator nothing shows up. It's just a blank white screen. Also in the debugger there are issues with constraints.

The main problem seems to be the stackView.distribution. I changed it from .fillProportionally to just .fill. I suspect the issue was that the views were already relative to each other because of your constraints and that lead to conflicts.
Other changes I made, your multiplier in one of your constraints needs to be 0.25 instead of 4, and in the second one the multiplier needs to be 1 instead of 0.
I also used NSLayoutConstraint.activate to activate the last 2 constraints instead of adding them to the view. In general, you want to activate constraints instead of adding them to views. Let iOS figure out which views to add them to.
class ViewController: UIViewController {
lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [smallRectangleView, bigRectangleView, bigRectangleView2])
stackView.alignment = .fill
stackView.distribution = .fill
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
var smallRectangleView: UIView = {
let view = UIView()
view.backgroundColor = .orange
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var bigRectangleView: UIView = {
let view = UIView()
view.backgroundColor = .green
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var bigRectangleView2: UIView = {
let view = UIView()
view.backgroundColor = .purple
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupLayout()
print(stackView.frame)
}
func setupLayout() {
// Stack View
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// Small Recntangle View
let heightConstraint1 = NSLayoutConstraint(item: smallRectangleView, attribute: .height, relatedBy: .equal, toItem: bigRectangleView, attribute: .height, multiplier: 0.25, constant: 0.0)
// Big Rectangle View
let heightConstraint2 = NSLayoutConstraint(item: bigRectangleView, attribute: .height, relatedBy: .equal, toItem: bigRectangleView2, attribute: .height, multiplier: 1.0, constant: 0.0)
NSLayoutConstraint.activate([heightConstraint1, heightConstraint2])
}
}

You can also continue to use iOS 9+ constraints instead of the older NSLayoutConstraint(item: without changing your original implementation:
func setupLayout() {
let topViewHeight:CGFloat = 50.0
let screenheight = view.bounds.height
// Stack View
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
smallRectangleView.heightAnchor.constraint(equalToConstant: topViewHeight).isActive = true
bigRectangleView.heightAnchor.constraint(equalToConstant: (screenheight - topViewHeight) / 2).isActive = true
bigRectangleView2.heightAnchor.constraint(equalToConstant: (screenheight - topViewHeight) / 2).isActive = true
}

Related

View not displaying and centralising with autolayout

I want to add a view in my view controller programmatically and centralise it. I added the view as subview to the parent view and enable autolayout yet its not showing.
import UIKit
class ViewController: UIViewController {
lazy var newView:UIView = {
let view = UIView()
view.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
view.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
return view
}()
let titleLable = UILabel()
let bodyLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(newView)
newView.translatesAutoresizingMaskIntoConstraints = false
newView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
newView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
}
Try this way
NSLayoutConstraint(item: newView,
attribute: NSLayoutConstraint.Attribute.centerX,
relatedBy: NSLayoutConstraint.Relation.equal,
toItem: view,
attribute: NSLayoutConstraint.Attribute.centerX,
multiplier: 1,
constant: 0).isActive = true
NSLayoutConstraint(item: newView,
attribute: NSLayoutConstraint.Attribute.centerY,
relatedBy: NSLayoutConstraint.Relation.equal,
toItem: view,
attribute: NSLayoutConstraint.Attribute.centerY,
multiplier: 1,
constant: 0).isActive = true
Size constraints are missing for newView. You removed them with newView.translatesAutoresizingMaskIntoConstraints = false. To restore all required constraints add the lines below to viewDidLoad:
newView.widthAnchor.constraint(equalToConstant: 200).isActive = true
newView.heightAnchor.constraint(equalToConstant: 200).isActive = true
here is the problem...
first, you set the frame of newView,
second, you also set translatesAutoresizingMaskIntoConstraints = false
so Xcode does not understand whats you want to set frame or constraints.
so if you set frame then you cannot add constraints again on it later.
so the best solution is here according to your need is:
view.addSubview(newView)
newView.translatesAutoresizingMaskIntoConstraints = false
newView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
newView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
newView.widthAnchor.constraint(equalToConstant: 200).isActive = true
newView.heightAnchor.constraint(equalToConstant: 200).isActive = true

UITextField is not filing available width inside UIStackView

I want to put two buttons with images on left and right edges of the screen, and insert a UITextField between them. In storyboard everything is fine, but I need to do it programmatically.
Source:
class SecondViewController: UIViewController {
override func loadView() {
super.loadView()
self.view.backgroundColor = .white
let leftButton = UIButton()
leftButton.setImage(UIImage(named: "first"), for: .normal)
leftButton.setTitle("", for: .normal)
let rightButton = UIButton()
rightButton.setImage(UIImage(named: "second"), for: .normal)
rightButton.setTitle("", for: .normal)
let textField = UITextField()
textField.placeholder = "clear"
textField.borderStyle = .roundedRect
textField.contentHorizontalAlignment = .left
textField.contentVerticalAlignment = .center
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.alignment = .fill
stackView.distribution = .fill
stackView.axis = .horizontal
stackView.spacing = 15
stackView.addArrangedSubview(leftButton)
stackView.addArrangedSubview(textField)
stackView.addArrangedSubview(rightButton)
self.view.addSubview(stackView)
NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 15).isActive = true
NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: -15).isActive = true
NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 50).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
I need a result like on first image.
But at result, I have second.
What did I do wrong ?
You have an elementary constraint ambiguity. You could easily have discovered this just by switching to the View Debugger; you would see three exclamation marks warning you of the problem. The layout engine does not magically know which of the three views should be allowed to stretch horizontally. You have to tell it:
textField.setContentHuggingPriority(UILayoutPriority(249), for: .horizontal)
Set constraints like
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50),
stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 15),
stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -15),
stackView.heightAnchor.constraint(equalToConstant: 45),
leftButton.widthAnchor.constraint(equalToConstant: 30),
rightButton.widthAnchor.constraint(equalToConstant: 30),
])
Another small change
rightButton.imageView?.contentMode = .scaleAspectFit
leftButton.imageView?.contentMode = .scaleAspectFit
Result

How can I add text in the center of a UIView Programmatically?

I have the following UIView, how can text be added in the center of this UIView programmatically?
This is the UIView code:
let newView = UIView()
newView.backgroundColor = .groupTableViewBackground
self.view.addSubview(newView)
newView.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
let guide = self.view.safeAreaLayoutGuide
newView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
newView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
newView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
newView.heightAnchor.constraint(equalToConstant: 50).isActive = true
} else {
NSLayoutConstraint(item: newView,
attribute: .top,
relatedBy: .equal,
toItem: view, attribute: .top,
multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: newView,
attribute: .leading,
relatedBy: .equal, toItem: view,
attribute: .leading,
multiplier: 1.0,
constant: 0).isActive = true
NSLayoutConstraint(item: newView, attribute: .trailing,
relatedBy: .equal,
toItem: view,
attribute: .trailing,
multiplier: 1.0,
constant: 0).isActive = true
newView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
This was my attempt at adding text to the center of the UIView programmatically, but I'm not sure why it is not working?
let lb = UILabel()
lb.centerXAnchor.constraint(equalTo: newView.centerXAnchor).isActive = true
lb.centerYAnchor.constraint(equalTo: newView.centerYAnchor).isActive = true
lb.text="anything"
newView.backgroundColor = UIColor.white
// show on screen
self.view.addSubview(newView)
newView.addSubview(lb)
lb.center = newView.center
UPDATE:
How can this button border below the UIView that's under the navigation controller be added?
When it comes to adding constraints to the view programmatically, note that you have to set translatesAutoresizingMaskIntoConstraints to false (but I get the feeling that you already know this, and just forgot to add it to the label)
let lb = UILabel()
lb.textAlignment = .center
lb.numberOfLines = 0
newView.addSubview(lb)
lb.translatesAutoresizingMaskIntoConstraints = false
lb.centerXAnchor.constraint(equalTo: newView.centerXAnchor).isActive = true
lb.centerYAnchor.constraint(equalTo: newView.centerYAnchor).isActive = true
Add border (assuming you want to add the border to the newView):
let border = UIView()
newView.addSubview(border)
border.translatesAutoresizingMaskIntoConstraints = false
border.leadingAnchor.constraint(equalTo: newView.leadingAnchor).isActive = true
border.trailingAnchor.constraint(equalTo: newView.trailingAnchor).isActive = true
border.bottomAnchor.constraint(equalTo: newView.bottomAnchor).isActive = true
border.heightAnchor.constraint(equalToConstant: 1).isActive = true
But for the border it would probably be cleaner to write an extension on UIView, so the code can be reusable in other places, if you ever need it again

I'm currently having trouble updating constraints for a UIVIew in Autolayout

I've created a custom View that has subviews added to it and a UIPanGestureRecognizer is added to each UIView object but when I select the UIView after I update the constraints there is a delay. If I drag the UIView (sView) off the screen, the next time I drag the view in either direction there is a delay of a few seconds before the pan gesture recognizer registers. It doesn't register the pan gesture immediately.
I've read up on auto layout and believe its configured correctly in the animation callback method. I've created a delegate in the view controller that has access to the updated constraints, and sView.leftConstraint and sView.rightConstraint are fields I have added to a custom view that represents a draggable UIView.
The ScrollViewController adopts a protocol so that it can access the view that was selected and update the constraints accordingly.
Any help would be greatly appreciated :)
extension ScrollViewController: DragDelegate {
func draggedLeft(sView: DraggableView) {
print("Dragged Left")
confirmationLabel.text = ""
sView.selectionLabel.text = "Not Selected"
sView.cornerRadius = 7
sView.rightConstraint?.isActive = false
sView.leftConstraint?.isActive = false
sView.leftConstraint?.constant = 1
sView.leftConstraint?.isActive = true
// sView.widthConstraint?.constant = 300
UIView.animate(withDuration: 0.3) {
sView.updateConstraints()
self.contentView.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
}
class OrderSelectionView: UIView {
var dView : DraggableView?
var dView2 : DraggableView?
let screenWidth = UIScreen.main.bounds.width
//I need to set up all the constraints in this class
//This will act almost like a container view
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
let margins = self.layoutMarginsGuide
self.translatesAutoresizingMaskIntoConstraints = false
dView = DraggableView(frame: frame)
dView2 = DraggableView(frame: frame)
guard let dragView = dView else {return}
guard let dragView2 = dView2 else {return}
dragView.translatesAutoresizingMaskIntoConstraints = false
dragView.topAnchor.constraint(equalTo: laundryLabel.bottomAnchor, constant: 10).isActive = true
let rightAnchor = NSLayoutConstraint(item: dragView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
let leftAnchor = NSLayoutConstraint(item: dragView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
dragView.widthAnchor.constraint(equalToConstant: 150).isActive = true
dragView.heightAnchor.constraint(equalToConstant: 40).isActive = true
dragView.leftConstraint = leftAnchor
dragView.leftConstraint?.isActive = true
dragView.rightConstraint = rightAnchor
self.addSubview(dragView2)
dragView2.translatesAutoresizingMaskIntoConstraints = false
let laundryLabel2 = createLaundryLabel(lTitle: "Dry", constant: 100)
dragView2.translatesAutoresizingMaskIntoConstraints = false
dragView2.topAnchor.constraint(equalTo: laundryLabel2.bottomAnchor, constant: 10).isActive = true
let rightAnchor2 = NSLayoutConstraint(item: dragView2, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: -50)
let leftAnchor2 = NSLayoutConstraint(item: dragView2, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
dragView2.widthAnchor.constraint(equalToConstant: 150).isActive = true
dragView2.heightAnchor.constraint(equalToConstant: 40).isActive = true
dragView2.leftConstraint = leftAnchor2
dragView2.leftConstraint?.isActive = true
dragView2.rightConstraint = rightAnchor2
}

How can I change a top constraint to another item in the layout programatically

So I set up a facebook button in iOS as ..
main_view.addSubview(FBlogBut)
NSLayoutConstraint(item: FBlogBut, attribute: .width, relatedBy: .equal, toItem: main_view, attribute: .width, multiplier: 0.95, constant: 0 ).isActive = true
NSLayoutConstraint(item: FBlogBut, attribute: .centerX, relatedBy: .equal, toItem: loginView, attribute: .centerX, multiplier: 1, constant: 0 ).isActive = true
topFBGuide = NSLayoutConstraint(item: FBlogBut, attribute: .top, relatedBy: .equal, toItem: login_button, attribute: .bottom, multiplier: 1, constant: 10 )
NSLayoutConstraint(item: FBlogBut, attribute: .bottom, relatedBy: .greaterThanOrEqual, toItem: loginView, attribute: .bottom, multiplier: 1, constant: 10 ).isActive = true
NSLayoutConstraint.activate([topFBGuide!])
main_view.layoutIfNeeded()
after a button click on the layout I do the following..
print("flip to register")
FBSDKLoginManager().logOut()
NSLayoutConstraint.deactivate([topFBGuide!])
topFBGuide2 = NSLayoutConstraint(item: FBlogBut, attribute: .top, relatedBy: .equal, toItem: register_button, attribute: .bottom, multiplier: 1, constant: 30 )
NSLayoutConstraint.activate([topFBGuide2!])
let buttonText = NSAttributedString(string: "Register with Facebook")
FBlogBut.setAttributedTitle(buttonText, for: .normal)
main_view.layoutIfNeeded()
So as you can see it is not moving correctly to the new constraint which should be 30 under the reg button
Thanks
R
Try using a StackView
Check below Code File
Instead of Changing constraints again and again Just Use a StackView and Set Login Button Constraint to StackView as I had Used
class ConstraintsViewController: UIViewController {
var TF1 = UITextField()
var TF2 = UITextField()
var TF3 = UITextField()
var TF4 = UITextField()
var toggleButton = UIButton()
var FBLoginBtn = UIButton()
var stackView = UIStackView()
var isChecked = true
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
self.setUpView()
}
func setUpView()
{
///Top Button as Toggle
toggleButton.backgroundColor = UIColor.lightGray
toggleButton.setTitle("Let's Toggle", for: .normal)
toggleButton.setTitleColor(UIColor.black, for: .normal)
toggleButton.addTarget(self, action: #selector(ConstraintsViewController.buttonAction(sender:)), for: .touchUpInside)
self.view.addSubview(toggleButton)
toggleButton.translatesAutoresizingMaskIntoConstraints = false
toggleButton.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
toggleButton.heightAnchor.constraint(equalToConstant: self.view.frame.size.height*0.06).isActive = true
toggleButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
toggleButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
///TF That you want to add
TF1.placeholder = "Email"
TF1.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF1.heightAnchor.constraint(equalToConstant: 30).isActive = true
TF2.placeholder = "Passowrd"
TF2.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF2.heightAnchor.constraint(equalToConstant: 30).isActive = true
TF3.placeholder = "ZipCode"
TF3.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF3.heightAnchor.constraint(equalToConstant: 30).isActive = true
TF4.placeholder = "Mobile Number"
TF4.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF4.heightAnchor.constraint(equalToConstant: 30).isActive = true
///Stack View that will store all the required TF
stackView.axis = UILayoutConstraintAxis.vertical
stackView.distribution = UIStackViewDistribution.fill
stackView.alignment = UIStackViewAlignment.center
stackView.spacing = 20
///Add 2 TF in StackView
stackView.addArrangedSubview(TF1)
TF1.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(TF2)
TF2.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
///Properties
stackView.translatesAutoresizingMaskIntoConstraints = false;
///Frame
stackView.topAnchor.constraint(equalTo: toggleButton.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: toggleButton.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: toggleButton.trailingAnchor).isActive = true
///UIButton
FBLoginBtn.backgroundColor = UIColor.lightGray
FBLoginBtn.setTitle("Login to Facebook", for: .normal)
FBLoginBtn.setTitleColor(UIColor.black, for: .normal)
self.view.addSubview(FBLoginBtn)
FBLoginBtn.translatesAutoresizingMaskIntoConstraints = false
FBLoginBtn.topAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
FBLoginBtn.heightAnchor.constraint(equalToConstant: 50).isActive = true
FBLoginBtn.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
FBLoginBtn.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
}
#objc func buttonAction(sender:UIButton)
{
isChecked = !isChecked
if isChecked {
self.stackView.removeArrangedSubview(TF3)
self.stackView.removeArrangedSubview(TF4)
TF3.removeFromSuperview()
TF4.removeFromSuperview()
} else {
self.stackView.addArrangedSubview(TF3)
self.stackView.addArrangedSubview(TF4)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Screenshot
When Loaded for First Time
When Button Is toggles
Helps you not to Update Constraints again and again

Resources