Programmatic layout : UIStackView alignment doesn't seem to work - swift

I have created a UIViewController with a UIStackView (vertical axis) wrapped in a UIScrollView that's pinned to the edges of the root View with auto-layout constraints.
I have also generated a number of UIButtons and added to the arranged subviews of the UIStackView.
I have tried to no avail to centre align the UIButtons in the UIStackView.
I'm not certain what i'm doing wrong.
Here's the code:
import UIKit
class ViewController: UIViewController {
var scrollView: UIScrollView!
var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: .AlignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: .AlignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .Vertical
stackView.alignment = .Center
scrollView.addSubview(stackView)
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[stackView]|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["stackView": stackView]))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[stackView]|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["stackView": stackView]))
for _ in 1 ..< 100 {
let vw = UIButton(type: UIButtonType.System)
vw.setTitle("Button", forState: .Normal)
stackView.addArrangedSubview(vw)
}
}
}

An extra equal width constraint between the scrollView and the stackView was needed. Like this:
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[stackView(==scrollView)]", options: .AlignAllCenterX, metrics: nil, views: ["stackView": stackView, "scrollView": scrollView]))
That did it for me.

How about:
stackView.Alignment = UIStackViewAlignment.Center
?

Related

UIScrollView constraints unexpected behaviour

I have a simple log in view implemented as follows :
import UIKit
class LoginViewController: UIViewController {
private var safeArea : UILayoutGuide!
private let scrollView : UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.keyboardDismissMode = .onDrag
return view
}()
private let containerView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let logoView : UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleAspectFill
view.layer.cornerRadius = 8
view.image = UIImage(named: "logo")!
return view
}()
private let emailOrPhoneTextFieldView : UITextField = {
let view = UITextField()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.5
view.layer.cornerRadius = 10
view.placeholder = "Email or phone"
view.font = UIFont.systemFont(ofSize: 16, weight: .regular)
view.textColor = .black
view.autocapitalizationType = .none
view.tintColor = UIColor(named: "myColor")
view.backgroundColor = .systemGray
return view
}()
private let passwordTextFieldView : UITextField = {
let view = UITextField()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.5
view.layer.cornerRadius = 10
view.placeholder = "Password"
view.font = UIFont.systemFont(ofSize: 16, weight: .regular)
view.textColor = .black
view.autocapitalizationType = .none
view.tintColor = UIColor(named: "myColor")
view.isSecureTextEntry = true
view.backgroundColor = .systemGray
return view
}()
private let logInButtonView : UIButton = {
let view = UIButton()
view.setTitle("Log in", for: .normal)
view.setTitleColor(.white, for : .normal)
view.setBackgroundImage( UIImage(named: "blue_pixel")!, for: .normal)
view.layer.cornerRadius = 10
view.layer.masksToBounds = true
view.translatesAutoresizingMaskIntoConstraints = false
view.addTarget(self, action: #selector(logInButtonClickedHandler), for: .touchUpInside)
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
safeArea = view.layoutMarginsGuide
setupViews()
}
private func setupViews()
{
view.addSubview(scrollView)
containerView.addSubview(logoView)
containerView.addSubview(emailOrPhoneTextFieldView)
containerView.addSubview(passwordTextFieldView)
containerView.addSubview(logInButtonView)
scrollView.addSubview(containerView)
let constraints = [
scrollView.topAnchor.constraint(equalTo: safeArea.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor),
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
logoView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 120),
logoView.widthAnchor.constraint(equalToConstant: 100),
logoView.heightAnchor.constraint(equalToConstant: 100),
logoView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
emailOrPhoneTextFieldView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
emailOrPhoneTextFieldView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
emailOrPhoneTextFieldView.topAnchor.constraint(equalTo: logoView.bottomAnchor, constant: 120),
emailOrPhoneTextFieldView.heightAnchor.constraint(equalToConstant: 50),
passwordTextFieldView.topAnchor.constraint(equalTo: emailOrPhoneTextFieldView.bottomAnchor),
passwordTextFieldView.leadingAnchor.constraint(equalTo: emailOrPhoneTextFieldView.leadingAnchor),
passwordTextFieldView.heightAnchor.constraint(equalToConstant: 50),
passwordTextFieldView.trailingAnchor.constraint(equalTo: emailOrPhoneTextFieldView.trailingAnchor),
logInButtonView.topAnchor.constraint(equalTo: passwordTextFieldView.bottomAnchor, constant: 16),
logInButtonView.leadingAnchor.constraint(equalTo: passwordTextFieldView.leadingAnchor),
logInButtonView.trailingAnchor.constraint(equalTo: passwordTextFieldView.trailingAnchor),
logInButtonView.heightAnchor.constraint(equalToConstant: 50),
logInButtonView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
]
NSLayoutConstraint.activate(constraints)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow(notification:)),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow(notification:)),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
#objc private func logInButtonClickedHandler() {
print("button pressed")
}
}
//MARK: Keyboard Notifications
private extension LoginViewController {
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
scrollView.contentInset.bottom = keyboardSize.height
scrollView.verticalScrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
}
}
#objc func keyboardWillHide(notification: NSNotification) {
scrollView.contentInset.bottom = .zero
scrollView.verticalScrollIndicatorInsets = .zero
}
}
Everything is fine with the implementation but 2 things looks very strange for me and I guess I misunderstood smth
If I comment out
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
I see that my container view does not fit the whole screen width (actually it's about 50% of it)
Why? I set trailing and leading constraints to scrollview, which is 100% of view width.
If I comment out
logInButtonView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
I don't get button click events and I'm not able to input anything inside textfields. What is the issue here?
From the Apple Docs:
Constraints between the edges or margins of the scroll view and its
content attach to the scroll view’s content area.
Constraints between the height, width, or centers attach to the scroll
view’s frame.
Hence you need the width constraint in order to make the contentView the full width of the ScrollView's frame.
As above, without that constraint the contentView only has constraints to the top/bottom edge of the scrollView this doesn't define its height and so you need to add full top-to-bottom constraints on the subviews of the contentView in order to define its height.
If you use the View Hierarchy Debugger you'll see the contentView has 0 height without that constraint (it just isn't clipping the content), hence why you can't tap on any controls.
It's worth giving the 'Working with Scroll Views' section of Apple Auto-Layout docs a read.

Hiding a view inside stackview still keeps constraints active

This code can be copy paste inside a newly created project:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = createLabel()
let imageView = createImageView()
let stackView = UIStackView(arrangedSubviews: [imageView, label])
stackView.axis = .vertical
stackView.spacing = 5
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (_) in
imageView.isHidden = true
}
}
func createLabel() -> UILabel {
let label = UILabel(frame: .zero)
label.text = "Some Text"
label.setContentHuggingPriority(.required, for: .horizontal)
label.setContentHuggingPriority(.required, for: .vertical)
label.backgroundColor = .green
return label
}
func createImageView() -> UIImageView {
let imageView = UIImageView()
imageView.backgroundColor = .red
imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
return imageView
}
}
It is a UILabel and a UIImageView inside a UIStackView. When I hide the UIImageView, I see that the UIStackView correctly adapts itselfs to the UILabel's height. However, the UIStackView does not adapts itself to the width of the UILabel.
How can I make the UIStackView resize itself to it's only visible views/UILabel? I made a variabele constraint for the UIImageView's height anchor constant and turning that off when hiding the UIImageView, but than the UILabel disappears for some odd reason.
Add stackview alignment
This property determines how the stack view lays out its arranged
views perpendicularly to its axis. The default value is
UIStackView.Alignment.fill.
stackView.alignment = .leading
Stackview resizes itself to it's only visible UILabel
Try changing the Distribution to Fill Equally in the UIStackView's Attribute Inspector and the Alignment to Fill or to center as you like it to be.

Swift Visual Format Language four buttons in centre

I would like to arrange four buttons with Visual Format Language around the central X an Y of a view without hard coding any points, preferring to scale with constraints.
I can only achieve a cluster of buttons to align to the bottom margin, how do I centre them with the spacing you see (e.g. ~20 points) without resorting to NSLayoutConstraint?
I did not place them in a stack, they are all separate buttons.
I read that stacks were not a good idea, but it seems like the logical way, otherwise they stretch out vertically.
Ideally I would like to use VFL to make a calculator UI but am trying this first.
#IBDesignable class images_and_constraints: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
calcButtons()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
calcButtons()
}
private func calcButtons() {
let calcPlus = UIButton()
calcPlus.translatesAutoresizingMaskIntoConstraints = false
calcPlus.setTitle("+", for: .normal)
calcPlus.setTitleColor(UIColor.black, for: .normal)
calcPlus.setTitleColor(UIColor.white, for: .highlighted)
calcPlus.backgroundColor = UIColor.orange
addSubview(calcPlus)
let calcSubtract = UIButton()
calcSubtract.translatesAutoresizingMaskIntoConstraints = false
calcSubtract.setTitle("-", for: .normal)
calcSubtract.setTitleColor(UIColor.black, for: .normal)
calcSubtract.setTitleColor(UIColor.white, for: .highlighted)
calcSubtract.backgroundColor = UIColor.orange
addSubview(calcSubtract)
let calcMultiply = UIButton()
calcMultiply.translatesAutoresizingMaskIntoConstraints = false
calcMultiply.setTitle("x", for: .normal)
calcMultiply.setTitleColor(UIColor.black, for: .normal)
calcMultiply.setTitleColor(UIColor.white, for: .highlighted)
calcMultiply.backgroundColor = UIColor.orange
addSubview(calcMultiply)
let calcDivide = UIButton()
calcDivide.translatesAutoresizingMaskIntoConstraints = false
calcDivide.setTitle("/", for: .normal)
calcDivide.setTitleColor(UIColor.black, for: .normal)
calcDivide.setTitleColor(UIColor.white, for: .highlighted)
calcDivide.backgroundColor = UIColor.orange
addSubview(calcDivide)
let views = ["calcPlus": calcPlus,
"calcSubtract": calcSubtract,
"calcMultiply": calcMultiply,
"calcDivide": calcDivide]
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcPlus]-[calcSubtract(==calcPlus)]-|",
options: .alignAllBottom,
metrics: nil,
views: views))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcMultiply]-[calcDivide(==calcMultiply)]-|",
options: .alignAllTop,
metrics: nil,
views: views))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calcSubtract]-[calcDivide(==calcSubtract)]-|",
options: .alignAllCenterX,
metrics: nil,
views: views))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:[calcSubtract]",
options: .alignAllCenterX,
metrics: nil,
views: views))
}
}
Using VFL to center views requires trickery.
Look at this question and particularly this answer for the trick.
For the kind of layout you want, VFL is just not a good fit.
Just one NSLayoutConstraint in addition to VFL would solve it but since you're only interested in VFL, I would suggest you use the trick to center a container view that holds your buttons.
Solution:
func calcButtons() {
//1. Create a container view that will contain your operator buttons
let buttonContainerView = UIView()
buttonContainerView.backgroundColor = UIColor.lightGray
buttonContainerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(buttonContainerView)
//Place it vertically in the center of the superview
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[superview]-(<=1)-[childView]",
options: .alignAllCenterX,
metrics: nil,
views: ["superview" : self,
"childView" : buttonContainerView]))
//Place it horizontally in the center of the superview + equal widths to superview
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:[superview]-(<=1)-[childView(==superview)]",
options: .alignAllCenterY,
metrics: nil,
views: ["superview" : self,
"childView" : buttonContainerView]))
//2. Create your buttons as you were:
//DRY Fix: Helper function to create button and add it to `buttonContainerView`
func addButton(title: String, selector: Selector? = nil) -> UIButton {
let button = UIButton()
button.backgroundColor = UIColor.orange
button.setTitle(title, for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.setTitleColor(UIColor.white, for: .highlighted)
//You might need this later cuz a button gotta do wat a button gotta do
if let selector = selector {
button.addTarget(self, action: selector, for: UIControlEvents.touchUpInside)
}
button.translatesAutoresizingMaskIntoConstraints = false
buttonContainerView.addSubview(button)
return button
}
let calcPlus = addButton(title: "+", selector: #selector(CalculatorView.add))
let calcSubtract = addButton(title: "-")
let calcMultiply = addButton(title: "x")
let calcDivide = addButton(title: "/")
let views = ["calcPlus": calcPlus,
"calcSubtract": calcSubtract,
"calcMultiply": calcMultiply,
"calcDivide": calcDivide]
//Same as before
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcPlus]-[calcSubtract(==calcPlus)]-|",
options: .alignAllBottom,
metrics: nil,
views: views))
//Same as before
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcMultiply]-[calcDivide(==calcMultiply)]-|",
options: .alignAllTop,
metrics: nil,
views: views))
/*
Same as before but this time we give a top constraint too
i.e.
"V:|-[calcSubtract]..."
instead of
"V:[calcSubtract]..."
*/
//
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[calcSubtract]-[calcDivide(==calcSubtract)]-|",
options: .alignAllCenterX,
metrics: nil,
views: views))
}
In the end I decided on NSLayoutConstraint.activate of which each button would be reliant on the one before it (rows), with the leading (far left for left-to-right readers) button constrained to the one above it.
calculatriceButtons["7"]!.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 1.0),
calculatriceButtons["7"]!.topAnchor.constraint(equalTo: calculatriceButtons["C"]!.bottomAnchor, constant: 1.0),
This was the best way to assure the buttons scaled on all devices.
There is a new alternative to using VFL which is what I use in code now.
Layout Anchors
Each view has different anchors. leading, trailing, top, bottom, etc...
You can use these to create constraints for you...
NSLayoutConstraint.activate([
viewB.leadingAnchor.constraint(equalTo: viewA.leadingAnchor, constant: 20),
viewA.widthAnchor.constraint(equalTo: viewB.widthAnchor)
])
for example.
Stack View
In addition to that there is an even more modern approach which is to use UIStackView. This is a really useful view that takes away the need to add constraints and does it for you.
let stackView = UIStackView(arrangedSubViews: [viewA, viewB])
stackView.spacing = 20
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fillEqually
You can also nest stack views to create more complex layouts.
Definitely worth looking in to...
https://developer.apple.com/documentation/uikit/uistackview?changes=_6
Creating your layout
let upperStackView = UIStackView(arrangedSubviews: [topLeft, topRight])
upperStackView.axis = .horizontal
upperStackView.distribution = .fillEqually
upperStackView.spacing = 20
let lowerStackView = UIStackView(arrangedSubviews: [bottomLeft, bottomRight])
lowerStackView.axis = .horizontal
lowerStackView.distribution = .fillEqually
lowerStackView.spacing = 20
let mainStackView = UIStackView(arrangedSubviews: [upperStackView, lowerStackView])
mainStackView.axis = .vertical
mainStackView.distribution = .fillEqually
mainStackView.spacing = 20
view.addSubview(mainStackView)
NSLayoutConstraint.activate([
mainStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mainStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
mainStackView.widthAnchor.constraint(equalToConstant: 200),
mainStackView.heightAnchor.constraint(equalToConstant: 200),
])
Why not VFL?
While VFL was a nice first attempt at AutoLayout, I feel that Apple has moved away from it now and are moving towards these more succinct methods of creating AutoLayout constraints.
It still allows you to think in constraints while writing code but provides a slightly more modern approach.
Of course... you can also create UIStackView in Interface Builder also :D

UIStackView setCustomSpacing at runtime

I have a horizontal UIStackView that, by default, looks as follows:
The view with the heart is initially hidden and then shown at runtime. I would like to reduce the spacing between the heart view and the account name view.
The following code does the job, but only, when executed in viewDidLoad:
stackView.setCustomSpacing(8, after: heartView)
When changing the custom spacing later on, say on a button press, it doesn't have any effect. Now, the issue here is, that the custom spacing is lost, once the subviews inside the stack view change: when un-/hiding views from the stack view, the custom spacing is reset and cannot be modified.
Things, I've tried:
verified the spacing is set by printing stackView.customSpacing(after: heartView) (which properly returns 8)
unsuccessfully ran several reload functions:
stackView.layoutIfNeeded()
stackView.layoutSubviews()
view.layoutIfNeeded()
view.layoutSubviews()
viewDidLayoutSubviews()
How can I update the custom spacing of my stack view at runtime?
You need to make sure the UIStackView's distribution property is set to .fill or .fillProportionally.
I created the following swift playground and it looks like I am able to use setCustomSpacing at runtime with random values and see the effect of that.
import UIKit
import PlaygroundSupport
public class VC: UIViewController {
let view1 = UIView()
let view2 = UIView()
let view3 = UIView()
var stackView: UIStackView!
public init() {
super.init(nibName: nil, bundle: nil)
}
public required init?(coder aDecoder: NSCoder) {
fatalError()
}
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view1.backgroundColor = .red
view2.backgroundColor = .green
view3.backgroundColor = .blue
view2.isHidden = true
stackView = UIStackView(arrangedSubviews: [view1, view2, view3])
stackView.spacing = 10
stackView.axis = .horizontal
stackView.distribution = .fillProportionally
let uiSwitch = UISwitch()
uiSwitch.addTarget(self, action: #selector(onSwitch), for: .valueChanged)
view1.addSubview(uiSwitch)
uiSwitch.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
uiSwitch.centerXAnchor.constraint(equalTo: view1.centerXAnchor),
uiSwitch.centerYAnchor.constraint(equalTo: view1.centerYAnchor)
])
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.heightAnchor.constraint(equalToConstant: 50),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 50),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -50)
])
}
#objc public func onSwitch(sender: Any) {
view2.isHidden = !view2.isHidden
if !view2.isHidden {
stackView.setCustomSpacing(CGFloat(arc4random_uniform(40)), after: view2)
}
}
}
PlaygroundPage.current.liveView = VC()
PlaygroundPage.current.needsIndefiniteExecution = true
Another reason setCustomSpacing can fail is if you call it before adding the arranged subview after which you want to apply the spacing.
Won't work:
headerStackView.setCustomSpacing(50, after: myLabel)
headerStackView.addArrangedSubview(myLabel)
Will work:
headerStackView.addArrangedSubview(myLabel)
headerStackView.setCustomSpacing(50, after: myLabel)
I also noticed that custom spacing values get reset after hiding/unhiding children. I was able to override updateConstraints() for my parent view and set the custom spacing as needed. The views then kept their intended spacing.
override func updateConstraints() {
super.updateConstraints()
stackView.setCustomSpacing(10, after: childView)
}

How do I add a label on top of a gradient?

I have a very basic UICollectionView. Inside my CollectionViewCell, I have a "thumbnailImage" which is just an image. I would like to have a gradient layer that fades from black, to a clear color BUT I would also like to have a UILabel ON TOP of this CAGradient and not underneath. The label is the "MovieTitle". I am programmatically doing everything, including the constraints. How do I perform this? Here is my code
let myView: UIView = {
let view = UIView()
return view
}()
let gradientView: CAGradientLayer = {
let grad = CAGradientLayer()
grad.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
grad.locations = [0.7, 1.2]
return grad
}()
func setupViews() {
thumbnailImageView.addSubview(movieTitle)
addSubview(thumbnailImageView)
thumbnailImageView.addSubview(dividerLine)
thumbnailImageView.addSubview(myView)
myView.layer.addSublayer(gradientView)
addConstrainsWithFormat(format: "H:|[v0]|", views: thumbnailImageView)
addConstrainsWithFormat(format: "V:|[v0]|", views: thumbnailImageView)
thumbnailImageView.addConstrainsWithFormat(format: "H:|[v0]|", views: myView)
thumbnailImageView.addConstrainsWithFormat(format: "V:|[v0]|", views: myView)
thumbnailImageView.addConstrainsWithFormat(format: "H:|[v0]|", views: dividerLine)
thumbnailImageView.addConstrainsWithFormat(format: "V:[v0(0.75)]|", views: dividerLine)
thumbnailImageView.addConstrainsWithFormat(format: "H:|-16-[v0]-16-|", views: movieTitle)
thumbnailImageView.addConstrainsWithFormat(format: "V:[v0(25)]-8-|", views: movieTitle)
}
Try to change the order of your views so that label appears above the view with gradient inside:
myView.layer.addSublayer(gradientView)
thumbnailImageView.addSubview(myView)
thumbnailImageView.addSubview(movieTitle)
thumbnailImageView.addSubview(dividerLine)
addSubview(thumbnailImageView)
You can also insert layers and views below or above already existing layers/views in your hierarchy:
view.insertSubview(subview, at: 0)
view.insertSubview(subview, belowSubview: existingView)
view.insertSubview(subview, aboveSubview: existingView)
layer.insertSublayer(gradientLayer, at: 0)
layer.insertSublayer(gradientLayer, below: anotherLayer)
layer.insertSublayer(gradientLayer, above: anotherLayer)