layout with before and after margins with equalToSystemSpacingAfter - ios

I’m trying to change some layouts I have to a number-less layout.
This is what I have for a segmented bar that should be inside a container view with something like this | - margin - segmented - margin -|
segmentedControl.leadingAnchor.constraint(equalToSystemSpacingAfter: margins.leadingAnchor, multiplier: 1),
segmentedControl.trailingAnchor.constraint(equalToSystemSpacingAfter: margins.trailingAnchor, multiplier: 1),
I know that the second line doesn’t make any sense, but I don’t see any equalToSystemSpacingBEFORE just after, and I’m not sure how to do it without having to rely only on layout propagation.
Basically, the leadingAchor works fine with this code, but the trailingAnchor (as the method name implies) adds the margin AFTER the trailing anchor, which is not what I want.
any ideas?

You can constrain the trailingAnchor of your "container" view relative to the trailingAnchor of your segmented control.
Here's a quick example which I believe gives you the layout you want:
class SysSpacingViewController: UIViewController {
let seg: UISegmentedControl = {
let v = UISegmentedControl(items: ["A", "B", "C"])
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let cView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
cView.addSubview(seg)
view.addSubview(cView)
let g = view.safeAreaLayoutGuide
let m = cView.layoutMarginsGuide
NSLayoutConstraint.activate([
cView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
cView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
cView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
cView.heightAnchor.constraint(equalToConstant: 70.0),
seg.leadingAnchor.constraint(equalToSystemSpacingAfter: m.leadingAnchor, multiplier: 1.0),
m.trailingAnchor.constraint(equalToSystemSpacingAfter: seg.trailingAnchor, multiplier: 1.0),
seg.centerYAnchor.constraint(equalTo: cView.centerYAnchor),
])
}
}
Result:

I think you can use this:
segmentedControl.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8).isActive = true
segmentedControl.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8).isActive = true
Please change the name of containerView and constants accordingly.

Related

Can you call addChild multiple times on UIViewController?

I'm working with a UIViewController and have two main displays of type UIViewController that I want to programmatically switch between. In order to do this I called addChild() two separate times upon instantiating the parent view controller.
Then later, when I want to switch between them I simply modify the isHidden property of each of them.
However, I believe this is causing some unintended behavior upon launch, where the UIViewController that is added first cannot be displayed without first displaying the UIViewController that's added second.
Doing research I couldn't find examples of people calling addChild() multiple times, and I was wondering if this is common practice or if there is a more common approach that might eliminate room for error. The documentation for addChild() says nothing about calling it multiple times. I'm quite new to Swift, but have experience with other programming languages, so I'm hoping to understand what I'm working with better.
There is no problem adding multiple child view controllers.
Here's how it could look (in viewDidLoad() of the "parent" view controller):
// instantiate 3 view controllers
let firstVC = FirstVC()
let secondVC = SecondVC()
let thirdVC = ThirdVC()
// for each view controller
[firstVC, secondVC, thirdVC].forEach { vc in
// add it as a child
self.addChild(vc)
// add its view
view.addSubview(vc.view)
// layout its view
vc.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// we'll constrain all 4 sides to the safe area
// with 20-points "padding" all around
vc.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
vc.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
vc.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
vc.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20.0),
])
// start with all child controller views hidden
vc.view.isHidden = true
// finish the add child process
vc.didMove(toParent: self)
}
View controllers maintain an array of "children" - so we can then "show" the first child's view like this:
// show first child controller view
self.children.first?.view.isHidden = false
Here's a quick, runnable example...
We create 3 view controllers, add them as child controllers, add their views (and set up constraints). Then we'll add a UISegmentedControl to switch between the child controllers' views.
So, it will look like this:
class MultiChildViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let seg = UISegmentedControl(items: ["First", "Second", "Third"])
seg.addTarget(self, action: #selector(segChanged(_:)), for: .valueChanged)
seg.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(seg)
// respect safe area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
seg.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 20.0),
seg.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 20.0),
seg.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -20.0),
])
// instantiate 3 view controllers
let firstVC = FirstVC()
let secondVC = SecondVC()
let thirdVC = ThirdVC()
// for each view controller
[firstVC, secondVC, thirdVC].forEach { vc in
// add it as a child
self.addChild(vc)
// add its view
view.addSubview(vc.view)
// layout its view
vc.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain top to segmented control bottom plus 20-points
vc.view.topAnchor.constraint(equalTo: seg.bottomAnchor, constant: 20.0),
// we'll constrain leading/trailing/bottom to the safe area
// with 20-points "padding"
vc.view.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 20.0),
vc.view.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -20.0),
vc.view.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -20.0),
])
// start with all child controller views hidden
vc.view.isHidden = true
// finish the add child process
vc.didMove(toParent: self)
}
// set the segmented control's selected segment to Zero
seg.selectedSegmentIndex = 0
// show first child controller view
self.children.first?.view.isHidden = false
}
#objc func segChanged(_ sender: UISegmentedControl) {
let idx = sender.selectedSegmentIndex
for (i, vc) in self.children.enumerated() {
vc.view.isHidden = i != idx
}
}
}
class FirstVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemRed
let v = UILabel()
v.textAlignment = .center
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.text = "First VC"
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
NSLayoutConstraint.activate([
v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
v.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
v.heightAnchor.constraint(equalToConstant: 100.0),
v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
let v = UILabel()
v.textAlignment = .center
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.text = "Second VC"
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
NSLayoutConstraint.activate([
v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
v.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
v.heightAnchor.constraint(equalToConstant: 100.0),
v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
class ThirdVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
let v = UILabel()
v.textAlignment = .center
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.text = "Third VC"
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
NSLayoutConstraint.activate([
v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
v.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
v.heightAnchor.constraint(equalToConstant: 100.0),
v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}

Font Character Height Change in iOS

I am trying to see if there is a proper method to change the height of the text, without changing the width - more akin to vertical compression. Photoshop lets you do it as shown below:
There are methods to change different attributes in text with NSAttributedString, but I didn't see one to change the height listed here:
https://developer.apple.com/documentation/foundation/nsattributedstring/key
You can do this by applying a scale CGAffineTransform.
Quick example using two identical labels, each with font set to .systemFont(ofSize: 32.0, weight: .regular), but the second label scaled to 50% height:
class ScaledLabelVC: UIViewController {
let v1 = UILabel()
let v2 = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
[v1, v2].forEach { v in
v.text = "This is a string."
v.font = .systemFont(ofSize: 32.0, weight: .regular)
v.backgroundColor = .yellow
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v1.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
v1.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
v1.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
v2.topAnchor.constraint(equalTo: v1.bottomAnchor, constant: 8.0),
v2.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
v2.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
v2.transform = CGAffineTransform(scaleX: 1.0, y: 0.5)
}
}
Result:

Combining constraints with variable and fixed heights on UIView in Swift

Update:
Thanks everyone for the unique approaches. Appreciate your insights.
Background:
I have written some code below in Xcode Swift 5 that creates four equal sized rectangles that resize depending on the device size and orientation.
However, the desired result is a rectangle with different heights, where the top rectangles are variable heights depending on the device size and orientation, and the bottom rectangles are with a constant height of 200px.
Question:
What changes to my code do I need to make to achieve variable heights on top and fixed heights on bottom?
Code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
view.addSubview(frameTopLeft)
frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
frameTopLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
view.addSubview(frameTopRight)
frameTopRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
frameTopRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
view.addSubview(frameBottomLeft)
frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
view.addSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
}
}
Images:
Simulator output.
Desired output.
Use StackView for better code and understanding.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let topStack = UIStackView()
topStack.axis = .horizontal
topStack.distribution = .fillEqually
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
topStack.addArrangedSubview(frameTopLeft)
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
topStack.addArrangedSubview(frameTopRight)
let bottomStack = UIStackView()
bottomStack.axis = .horizontal
bottomStack.distribution = .fillEqually
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
bottomStack.addArrangedSubview(frameBottomLeft)
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
bottomStack.addArrangedSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
frameBottomRight.heightAnchor.constraint(equalToConstant: 200).isActive = true
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.addArrangedSubview(topStack)
mainStack.addArrangedSubview(bottomStack)
self.view.addSubview(mainStack)
mainStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: view.topAnchor),
mainStack.heightAnchor.constraint(equalTo: view.heightAnchor),
mainStack.leftAnchor.constraint(equalTo: view.leftAnchor),
mainStack.widthAnchor.constraint(equalTo: view.widthAnchor)
])
}
}
Or you can give relative constraints to each bottom view.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
view.addSubview(frameTopLeft)
frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
// frameTopLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5), << Here
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
view.addSubview(frameTopRight)
frameTopRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
// frameTopRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5), << Here
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
view.addSubview(frameBottomLeft)
frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomLeft.topAnchor.constraint(equalTo: frameTopLeft.bottomAnchor), // << Here
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.heightAnchor.constraint(equalToConstant: 200), // << Here
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
view.addSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomRight.topAnchor.constraint(equalTo: frameTopRight.bottomAnchor), // << Here
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.heightAnchor.constraint(equalToConstant: 200), // << Here
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
}
}
You want to give the Bottom views constant Height constraints of 200, and then constrain the Bottoms of the top views to the Tops of the Bottom views.
You may find it helpful to group related code together, and add comments as you go (to make it a little clearer what you believe the code should do).
So, try it like this:
override func viewDidLoad() {
super.viewDidLoad()
// create 4 views
let frameTopLeft = UIView()
let frameTopRight = UIView()
let frameBottomLeft = UIView()
let frameBottomRight = UIView()
// set background colors
frameTopLeft.backgroundColor = .systemRed
frameTopRight.backgroundColor = .systemBlue
frameBottomLeft.backgroundColor = .systemGreen
frameBottomRight.backgroundColor = .systemYellow
// set Autoresizing and add to view
[frameTopLeft, frameTopRight, frameBottomLeft, frameBottomRight].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
NSLayoutConstraint.activate([
// top-left view constrained Top / Left / 50% Width
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// top-right view constrained Top / Right / 50% Width
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// bottom-left view constrained Bottom / Left / 50% Width
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// bottom-right view constrained Bottom / Right / 50% Width
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// bottom views, constant Height of 200-pts
frameBottomLeft.heightAnchor.constraint(equalToConstant: 200.0),
frameBottomRight.heightAnchor.constraint(equalToConstant: 200.0),
// constrain bottoms of top views to tops of bottom views
frameTopLeft.bottomAnchor.constraint(equalTo: frameBottomLeft.topAnchor),
frameTopRight.bottomAnchor.constraint(equalTo: frameBottomRight.topAnchor),
])
}
I'd do it like this:
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
view.addSubview(frameTopLeft)
frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
])
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
view.addSubview(frameTopRight)
frameTopRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.leftAnchor.constraint(equalTo: frameTopLeft.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: frameTopLeft.widthAnchor)
])
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
view.addSubview(frameBottomLeft)
frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomLeft.topAnchor.constraint(equalTo: frameTopLeft.bottomAnchor),
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.heightAnchor.constraint(equalToConstant: 200),
])
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
view.addSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomRight.topAnchor.constraint(equalTo: frameTopRight.bottomAnchor),
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.leftAnchor.constraint(equalTo: frameBottomLeft.rightAnchor),
frameBottomRight.heightAnchor.constraint(equalTo: frameBottomLeft.heightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: frameBottomLeft.widthAnchor),
])
The main diff from others is getting rid of constants as much as possible.
Your bottom views should be 200? Add single 200 constraint, and make second view height be equal to the first one.
Instead of making width equal 50% of parent, you can make left width be equal right one
It makes you code easier to maintain, decreasing places to edit.

constraints not working programmatically swift

func textFields() {
let nameField = MDCFilledTextField()
view.addSubview(nameField)
nameField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
nameField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
nameField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
nameField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
nameField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
nameField.placeholder = "Name"
}
I am using google material design and create a textfield.For this purpose and i have used this code i want to space from both sides left and right but it works only one side left or right i want to space from both side.
You are trying to set the MDCFilledTextField position in a wrong way. The following line tells your view to use a static predefined frame size:
let estimatedFrame = CGRect(x: 10, y: 200, width: UIScreen.main.bounds.width-20, height: 50)
let nameField = MDCFilledTextField(frame: estimatedFrame)
But further down with the following rows you tell your view to use autolayout:
nameField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
nameField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
nameField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
nameField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
nameField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
So now you have to decide which one would you like to use, the static frame or the autolayout. If you decide to go with the autolayout you have to improve your code in order to remove the error you get. First you need to set the translatesAutoresizingMaskIntoConstraints to true instead of false.
nameField.translatesAutoresizingMaskIntoConstraints = false
This line will tell that you want to use the autolayout for nameField instead of a static frame. Further you need to add your view to the superview first, otherwise you can't define your constraints(therefore the error you have). So your code becomes:
func textFields() {
let nameField = MDCFilledTextField()
view.addSubview(nameField)
nameField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
nameField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
nameField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
nameField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
nameField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
nameField.placeholder = "Name"
}
Add the nameField as a subview of the view before activating the constraint and remove of using frames.
To add padding use for example centerXAnchor and widthAnchor + multiplier
func textFields() {
let nameField = MDCFilledTextField()
nameField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(nameField)
NSLayoutConstraint.activate([
nameField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
nameField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
nameField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
nameField.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9)
])
nameField.placeholder = "Name"
nameField.label.text = "Name"
nameField.setFloatingLabelColor(.lightGray, for: MDCTextControlState.editing)
}
or add constants to the leftAnchor and rightAnchor.
func textFields() {
let nameField = MDCFilledTextField()
nameField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(nameField)
NSLayoutConstraint.activate([
nameField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
nameField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
nameField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10),
nameField.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10)
])
nameField.placeholder = "Name"
nameField.label.text = "Name"
nameField.setFloatingLabelColor(.lightGray, for: MDCTextControlState.editing)
}

Using ScrollView with StackView as subview and UIViews as children of StackView

I'm struggling to get my scroll view to work programatically.
I have a view controller that instantiates a UIScrollView with the following constraints
class HomeTabBarController: ViewController {
let homePageView = HomePageView()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
homePageView.setupHomePage()
view.addSubview(homePageView)
///constraints
homePageView.stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
homePageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
homePageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true;
homePageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true;
homePageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -83).isActive = true;
}
}
The HomePageView (UIScrollView) has a UIStackView and instantiates 3 more UIViews which are the UIStackView's children. The code and constraints are as follows
class HomePageView: UIScrollView {
var homePageCarrousel: HomePageCarrousel?
var homePageSocial: HomePageSocialUp?
var homePageAboutUs: HomePageAboutUs?
var stackView = UIStackView()
func setupHomePage() {
translatesAutoresizingMaskIntoConstraints = false
homePageCarrousel = HomePageCarrousel()
homePageSocial = HomePageSocialUp()
homePageAboutUs = HomePageAboutUs()
guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }
homePageSocial.setupSocialHeader()
stackView.addArrangedSubview(homePageSocial)
homePageCarrousel.setupCarrousel()
stackView.addArrangedSubview(homePageCarrousel)
homePageAboutUs.setup()
stackView.addArrangedSubview(homePageAboutUs)
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 10
setLayout()
}
func setLayout(){
guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }
///header
homePageSocial.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
homePageSocial.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
homePageSocial.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
///carrousel
homePageCarrousel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 20).isActive = true
homePageCarrousel.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 20).isActive = true
homePageCarrousel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -20).isActive = true
///about us
homePageAboutUs.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
homePageAboutUs.topAnchor.constraint(equalTo: homePageCarrousel.bottomAnchor, constant: 10).isActive = true
homePageAboutUs.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
homePageAboutUs.bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
///stackview
self.stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true;
self.stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true;
self.stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true;
self.stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true;
}
func dispose() {
homePageSocial = nil
homePageCarrousel = nil
homePageAboutUs = nil
subviews.forEach{$0.removeFromSuperview()}
}
}
Each of the children UIView (homePageSocial, homePageCarrousel and homePageAboutUs) have constraints have also got constraints:
HomePageCarrousel
Loads UIImageView and after adding as subview sets the constraint as
heightAnchor.constraint(equalToConstant: imageView.frame.height).isActive = true
HomePageAboutUs
Has 3 UITextviews (headerText, bodyTextLeft, bodyTextRight)
Header on top, bodytextLeft below it being 50% width of screen x = 0 and bodyTextRight x = width of bodyTextLeft.
Constraints are as follows
heightAnchor.constraint(equalToConstant: headerText.frame.height + bodyTextLeft.frame.height).isActive = true
bodyTextLeft.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 15).isActive = true
bodyTextLeft.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / 2).isActive = true
bodyTextRight.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 15).isActive = true
bodyTextRight.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / 2).isActive = true
bodyTextRight.leadingAnchor.constraint(equalTo: bodyTextLeft.trailingAnchor).isActive = true
HomePageSocialUp
A basic header with an icon and a constraint of
heightAnchor.constraint(equalToConstant: 325).isActive = true
After all that set I still can't get my ui scroll view to scroll on the y axis leaving my text of homePageAboutUs below the tabBar and off screen.
What am I doing wrong here?
Thanks in advance
First - the main purpose of a UIStackView is to arrange its subviews, so it is wrong to add position constraints to those subviews.
Next, when adding subviews to the "root" view of a controller (such as your scroll view), be sure to constrain them to the Safe Area Layout Guide.
Third, constrain the content of your scroll view to its Content Layout Guide.
I'm kind of taking your descriptions and hoping I'm close to what you're going for here:
and after scrolling down:
Here is your code, modified to produce that result:
class HomeTabBarController: UIViewController {
let homePageView = HomePageView()
override func viewDidLoad() {
super.viewDidLoad()
homePageView.setupHomePage()
view.addSubview(homePageView)
// respect safe area
let g = view.safeAreaLayoutGuide
///constraints
NSLayoutConstraint.activate([
homePageView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
homePageView.topAnchor.constraint(equalTo: g.topAnchor),
homePageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
homePageView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
}
}
class HomePageSocialUp: UIView {
func setupSocialHeader() -> Void {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .red
let imgView = UIImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imgView)
NSLayoutConstraint.activate([
// constrain image view 20-pts on each side
imgView.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
imgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
imgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20.0),
// Height = 325
imgView.heightAnchor.constraint(equalToConstant: 325.0),
])
if let img = UIImage(named: "myHeaderImage") {
imgView.image = img
}
}
}
class HomePageCarrousel: UIView {
func setupCarrousel() -> Void {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .green
let imgView = UIImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imgView)
NSLayoutConstraint.activate([
// constrain image view 20-pts on each side
imgView.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
imgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
imgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20.0),
// let's make it 3:2 ratio
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: 2.0 / 3.0)
])
if let img = UIImage(named: "myCarouselImage") {
imgView.image = img
}
}
}
class HomePageAboutUs: UIView {
let headerText = UILabel()
let bodyTextLeft = UILabel()
let bodyTextRight = UILabel()
func setup() -> Void {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .blue
[headerText, bodyTextLeft, bodyTextRight].forEach {
// keep label height to text content
$0.setContentHuggingPriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .vertical)
// allow word-wrap
$0.numberOfLines = 0
// yellow background
$0.backgroundColor = .yellow
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
NSLayoutConstraint.activate([
// header text 8-pts from Top / Leading / Trailing
headerText.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
headerText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
headerText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// left text 8-pts from Bottom of header text, 8-pts Leading
bodyTextLeft.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 8.0),
bodyTextLeft.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
// right text 8-pts from Bottom of header text, 8-pts Trailing
bodyTextRight.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 8.0),
bodyTextRight.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// 8-pts between left and right text
bodyTextRight.leadingAnchor.constraint(equalTo: bodyTextLeft.trailingAnchor, constant: 8.0),
// left and right text equal width
bodyTextLeft.widthAnchor.constraint(equalTo: bodyTextRight.widthAnchor),
// constrain Bottom of both to <= 8 (at least 8-pts
bodyTextLeft.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
bodyTextRight.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
])
headerText.font = .systemFont(ofSize: 20, weight: .regular)
bodyTextLeft.font = .systemFont(ofSize: 16, weight: .regular)
bodyTextRight.font = .systemFont(ofSize: 16, weight: .regular)
// texxt alignment
headerText.textAlignment = .center
bodyTextLeft.textAlignment = .left
bodyTextRight.textAlignment = .right
// some sample text
headerText.text = "This is the text for the About Us Header label. It will, of course, wrap onto multiple lines when needed, and auto-size it's height to fit the text."
bodyTextLeft.text = "Left label with\nembedded newlines\nso we can see it grow\nto fit the text.\nLine 5\nLine 6\nLine 7"
bodyTextRight.text = "Right label will wrap if needed. The one with the most lines will determine the bottom."
}
}
class HomePageView: UIScrollView {
var homePageCarrousel: HomePageCarrousel?
var homePageSocial: HomePageSocialUp?
var homePageAboutUs: HomePageAboutUs?
var stackView = UIStackView()
func setupHomePage() {
translatesAutoresizingMaskIntoConstraints = false
homePageCarrousel = HomePageCarrousel()
homePageSocial = HomePageSocialUp()
homePageAboutUs = HomePageAboutUs()
guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }
homePageSocial.setupSocialHeader()
stackView.addArrangedSubview(homePageSocial)
homePageCarrousel.setupCarrousel()
stackView.addArrangedSubview(homePageCarrousel)
homePageAboutUs.setup()
stackView.addArrangedSubview(homePageAboutUs)
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 10
setLayout()
}
func setLayout(){
// constrain stackView to scroll view's Content Layout Guide
let g = self.contentLayoutGuide
///stackview
NSLayoutConstraint.activate([
self.stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
self.stackView.topAnchor.constraint(equalTo: g.topAnchor),
self.stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
self.stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
// stack view width is scroll view's frame layout guide width
stackView.widthAnchor.constraint(equalTo: self.frameLayoutGuide.widthAnchor),
])
}
}

Resources