Same constraints different results - swift

I gave a value of -8 to the "bottom to Safe Area bottom" constraints of 2 views, but they are not showing at the same distance from the Safe Area (seems like one needs -16 to equalize them).
What could be the issue?
Here's some code:
func addPlayerViews() { // called in viewDidLoad
DispatchQueue.main.async {
self.smallView.backgroundColor = .clear
self.smallView.myLittleSubview.backgroundColor = .white
self.smallView.myLittleSubview.layer.cornerRadius = 4
self.smallView.myLittleSubview.layer.shadowColor = myShadowColor
self.smallView.myLittleSubview.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
self.smallView.myLittleSubview.layer.masksToBounds = false
self.smallView.myLittleSubview.layer.shadowOpacity = 1.0
self.smallView.myLittleSubview.layer.shadowRadius = 4
self.bigView.backgroundColor = .white
self.bigView.layer.cornerRadius = 4
self.bigView.layer.shadowColor = myShadowColor
self.bigView.layer.shadowOffset = CGSize(width: 0.0, height: -3.0)
self.bigView.layer.masksToBounds = false
self.bigView.layer.shadowOpacity = 1.0
self.bigView.layer.shadowRadius = 4
self.view.addSubview(self.smallView)
self.view.addSubview(self.bigView)
self.smallView.translatesAutoresizingMaskIntoConstraints = false
self.bigView.translatesAutoresizingMaskIntoConstraints = false
let safeArea = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
self.smallView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 4),
self.smallView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -4),
self.smallView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -8),
self.smallView.heightAnchor.constraint(equalToConstant: 48),
self.bigView.leadingAnchor.constraint(
equalTo: self.smallView.myLittleSubview.leadingAnchor, constant: 0),
self.bigView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -4),
self.bigView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -8),
// ☝️ not working as expected. See: 👉 https://stackoverflow.com/q/62602071/5306470
self.bigView.heightAnchor.constraint(equalToConstant: 270)
])
self.view.layoutIfNeeded() // must be at end of animation block, it seems
}
}
Screenshot
Big view is blue and semi-transparent, myLittleSubview is orange, and smallView is .clear
iPhone 11 Pro Max (13.5)

Related

The UI search bar disappears when clicked( possibly NSLayoutConstraint issue)

A quick question, for what should've been an easy implementation.
Im trying to implement a UISearchcontroller and the UIsearchbar property when trying to customize it and set constraints behaves properly appears perfect but the minute i click on the search bar it resets its constraints to nil(guessing based on visual debugger).
Before clicking
and here is the second image which shows what happens when clicked
After clicking
Ive been trying to do this for a day now.
Context: My Main VC is a collection view and another button.
below is the search view specific code, I tried isolating the issue in a playground file and noticed issue starts when i add constraints to it.
var searchController:UISearchController!
private func setupSearchView(){
let viewController = UISearchController(searchResultsController: nil)
viewController.delegate = self
let bar = viewController.searchBar
bar.delegate = self
bar.searchBarStyle = .minimal
bar.translatesAutoresizingMaskIntoConstraints=false
bar.searchTextField.layer.cornerRadius = 20
bar.searchTextField.textColor = .darkGray
bar.searchTextField.backgroundColor = .white
bar.showsCancelButton = false
bar.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
bar.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
bar.layer.shadowOpacity = 1.0
bar.layer.shadowRadius = 0.0
bar.layer.masksToBounds = false
guard let customFont = UIFont(name: "Poppins-SemiBold", size: 14.0) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)}
bar.searchTextField.font=customFont
self.searchController = viewController
self.view.addSubview(bar)
bar.isHidden = true
}
func setupContstraints() {
//NSLayoutConstrainst
let searchBar:UISearchBar=searchController!.searchBar
NSLayoutConstraint.activate([
searchButton!.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
searchButton!.topAnchor.constraint(equalTo: view.topAnchor, constant: 30),
searchButton!.widthAnchor.constraint(equalToConstant: 50),
searchButton!.heightAnchor.constraint(equalToConstant: 50),
//search bar
searchBar.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
searchBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 30),
searchBar.widthAnchor.constraint(equalToConstant: 170.0),
searchBar.heightAnchor.constraint(equalToConstant: 50.0)]
)
Update:
When i gave the search bar fixed width and height(not ideal for different device size) it now appears with the width and height but doesn't obey the top anchor constraint.
See current image
also updated the snippet with current constraints
Give a fixed width and height value for your "SearchBar" object. It will probably be fixed.
It may take up as much width as the cursor when clicked.
Comment out or delete the 2 lines below while giving a fixed height and width for the searchBar.
searchBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 50),
searchBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -100)
Rid of the UISearchController and use a UISearchBar instead.
let searchBar = UISearchBar()
let clearButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
setupSearchView()
}
private func setupSearchView(){
searchBar.searchBarStyle = .minimal
searchBar.translatesAutoresizingMaskIntoConstraints=false
searchBar.searchTextField.layer.cornerRadius = 20
searchBar.searchTextField.textColor = .darkGray
searchBar.searchTextField.backgroundColor = .white
searchBar.showsCancelButton = false
searchBar.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
searchBar.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
searchBar.layer.shadowOpacity = 1.0
searchBar.layer.shadowRadius = 0.0
searchBar.layer.masksToBounds = false
// guard let customFont = UIFont(name: "Poppins-SemiBold", size: 14.0) else {
// fatalError("""
// Failed to load the "CustomFont-Light" font.
// Make sure the font file is included in the project and the font name is spelled correctly.
// """
// )}
// bar.searchTextField.font=customFont
// set your search clear button
clearButton.setImage(UIImage(systemName: "multiply.circle.fill"), for: .normal)
clearButton.tintColor = .red
let searchStack = UIStackView(arrangedSubviews: [searchBar, clearButton])
searchStack.axis = .horizontal
searchStack.distribution = .fill
searchStack.alignment = .fill
searchStack.spacing = 16
searchStack.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(searchStack)
// bar.isHidden = true
NSLayoutConstraint.activate([
clearButton.heightAnchor.constraint(equalTo: clearButton.widthAnchor),
searchStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30),
searchStack.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30),
searchStack.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -30),
searchStack.heightAnchor.constraint(equalToConstant: 50)
])
}

How to layout 2 StackView of different sizes in a ScrollView

I am trying to fit 2 stackView in one ScrollView
I decided to create a generic StackView that will contain the other two StackViews and place it in a ScrollView
But my left StackView contains 1 item and the second contains 2 items
Code bellow:
let categoryViewsForLeftColumn = categoryViews.split()[0]
let categoryViewsForRightColumn = categoryViews.split()[1]
let leftColumnStackView = UIStackView(arrangedSubviews: categoryViewsForLeftColumn)
leftColumnStackView.translatesAutoresizingMaskIntoConstraints = false
leftColumnStackView.axis = .vertical
leftColumnStackView.spacing = 20
leftColumnStackView.distribution = .fillEqually
let rightColumnStackView = UIStackView(arrangedSubviews: categoryViewsForRightColumn)
rightColumnStackView.translatesAutoresizingMaskIntoConstraints = false
rightColumnStackView.axis = .vertical
rightColumnStackView.spacing = 20
rightColumnStackView.distribution = .fillEqually
let generalStackView = UIStackView(arrangedSubviews: [leftColumnStackView, rightColumnStackView])
generalStackView.translatesAutoresizingMaskIntoConstraints = false
generalStackView.axis = .horizontal
generalStackView.spacing = 20
generalStackView.distribution = .fillEqually
categoriesScrollView.addSubview(generalStackView)
leftColumnStackView.backgroundColor = .green
rightColumnStackView.backgroundColor = .red
NSLayoutConstraint.activate([
generalStackView.widthAnchor.constraint(equalTo: categoriesScrollView.widthAnchor, constant: -10),
generalStackView.leadingAnchor.constraint(equalTo: categoriesScrollView.leadingAnchor, constant: 5),
generalStackView.trailingAnchor.constraint(equalTo: categoriesScrollView.trailingAnchor, constant: -5),
generalStackView.topAnchor.constraint(equalTo: categoriesScrollView.topAnchor, constant: 5),
generalStackView.bottomAnchor.constraint(equalTo: categoriesScrollView.bottomAnchor, constant: -5)
])
result bellow:
I expect items to be the same size
I was trying to post 2 StackView to ScrollView
code bellow:
categoriesScrollView.addSubview(leftColumnStackView)
categoriesScrollView.addSubview(rightColumnStackView)
leftColumnStackView.backgroundColor = .green
rightColumnStackView.backgroundColor = .red
NSLayoutConstraint.activate([
leftColumnStackView.leadingAnchor.constraint(equalTo: categoriesScrollView.leadingAnchor, constant: 5),
leftColumnStackView.trailingAnchor.constraint(equalTo: categoriesScrollView.centerXAnchor, constant: -5),
leftColumnStackView.topAnchor.constraint(equalTo: categoriesScrollView.topAnchor, constant: 5),
leftColumnStackView.bottomAnchor.constraint(equalTo: categoriesScrollView.bottomAnchor, constant: -5),
rightColumnStackView.leadingAnchor.constraint(equalTo: categoriesScrollView.centerXAnchor, constant: 5),
rightColumnStackView.trailingAnchor.constraint(equalTo: categoriesScrollView.trailingAnchor, constant: -5),
rightColumnStackView.topAnchor.constraint(equalTo: categoriesScrollView.topAnchor, constant: 5),
rightColumnStackView.bottomAnchor.constraint(equalTo: categoriesScrollView.bottomAnchor, constant: -5)
])
But it only got worse
result bellow:
The result I want to achieve:
Does anyone know how to achieve this?
If you want to create a "grid" like that without using a collection view (and there are many good reasons not to), you can do it with stack views.
However, instead of think in terms of a horizontal stack view with two vertical "column" stack views, think about a vertical stack view with "rows" of horizontal stack views.
Here's a quick example...
First, a custom view with rounded sides, an image view and a label:
class PillView: UIView {
let imgView = UIImageView()
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
label.numberOfLines = 0
imgView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(imgView)
addSubview(label)
NSLayoutConstraint.activate([
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0),
imgView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 12.0),
imgView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -12.0),
imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
imgView.widthAnchor.constraint(equalToConstant: 36.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
label.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
label.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 8.0),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0),
])
layer.borderColor = UIColor.lightGray.cgColor
layer.borderWidth = 1
}
override var bounds: CGRect {
get { return super.bounds }
set(newBounds) {
super.bounds = newBounds
layer.cornerRadius = newBounds.size.height / 2.0
}
}
}
Now, a sample controller:
class SampleViewController: UIViewController {
let numViews: Int = 5
override func viewDidLoad() {
super.viewDidLoad()
let outerVerticalStack = UIStackView()
outerVerticalStack.axis = .vertical
outerVerticalStack.spacing = 20
outerVerticalStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(outerVerticalStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
outerVerticalStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
outerVerticalStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
outerVerticalStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
var j: Int = 0
while j < numViews {
let rowStack = UIStackView()
rowStack.axis = .horizontal
rowStack.spacing = 20
rowStack.distribution = .fillEqually
let v = PillView()
if j == 2 {
v.label.text = "View \(j)\ntwo lines"
} else {
v.label.text = "View \(j)"
}
if let img = UIImage(systemName: "\(j).square.fill") {
v.imgView.image = img
}
rowStack.addArrangedSubview(v)
j += 1
if j < numViews {
let v = PillView()
v.label.text = "View \(j)"
if let img = UIImage(systemName: "\(j).square.fill") {
v.imgView.image = img
}
rowStack.addArrangedSubview(v)
} else {
let v = UIView()
rowStack.addArrangedSubview(v)
}
j += 1
outerVerticalStack.addArrangedSubview(rowStack)
}
}
}
The result looks like this:

The shadow added to the view created is not appearing

I am adding programatically a UIView, and setting its contsraints using NSLayoutConstraint as below, yet teh shadow is not being added.
For the shadow i am using SwifterSwift .addShadow()
UiView:
lazy var alertViewNew: UIView = {
let view = UIView()
view.layer.zPosition = 1
view.cornerRadius = 20
view.addShadow(ofColor: .lightGray, radius: 3, offset: .zero, opacity: 0.3)
view.translatesAutoresizingMaskIntoConstraints = false
return alertView
}()
Adding the Constaraints
func setUpAlertView() {
[alertViewNew].forEach {
(view.addSubview($0))
}
NSLayoutConstraint.activate([
alertViewNew.centerYAnchor.constraint(equalTo: view.centerYAnchor),
alertViewNew.centerXAnchor.constraint(equalTo: view.centerXAnchor),
alertViewNew.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
alertViewNew.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
titleLabel.leadingAnchor.constraint(equalTo: alertViewNew.leadingAnchor, constant: 20),
titleLabel.trailingAnchor.constraint(equalTo: alertViewNew.trailingAnchor, constant: -20),
titleLabel.topAnchor.constraint(equalTo: alertViewNew.topAnchor, constant: 20),
descriptionLabel.leadingAnchor.constraint(equalTo: alertViewNew.leadingAnchor, constant: 20),
descriptionLabel.trailingAnchor.constraint(equalTo: alertViewNew.trailingAnchor, constant: -20),
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
updateButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 5),
updateButton.trailingAnchor.constraint(equalTo: alertViewNew.trailingAnchor, constant: -20),
updateButton.widthAnchor.constraint(equalToConstant: 65),
updateButton.bottomAnchor.constraint(equalTo: alertViewNew.bottomAnchor, constant: -20),
])
}
AddShadow by Swifter Swift
func addShadow(ofColor color: UIColor = UIColor(red: 0.07, green: 0.47, blue: 0.57, alpha: 1.0), radius: CGFloat = 3, offset: CGSize = .zero, opacity: Float = 0.5) {
layer.shadowColor = color.cgColor
layer.shadowOffset = offset
layer.shadowRadius = radius
layer.shadowOpacity = opacity
layer.masksToBounds = false
}
Things i tried to fix the issue
Setting mask to bound to true
setting is opaque to true
and some other trials found on stackoverflow
None of this worked
It's a bit difficult to help, because the code you've shown is incomplete (and has errors, as written).
For example, I assume your func addShadow is in a UIView extension like this:
extension UIView {
func addShadow(ofColor color: UIColor = UIColor(red: 0.07, green: 0.47, blue: 0.57, alpha: 1.0), radius: CGFloat = 3, offset: CGSize = .zero, opacity: Float = 0.5) {
layer.shadowColor = color.cgColor
layer.shadowOffset = offset
layer.shadowRadius = radius
layer.shadowOpacity = opacity
// no need for this
//layer.masksToBounds = false
}
}
Next, your lazy var alertViewNew:
lazy var alertViewNew: UIView = {
let view = UIView()
// no logical reason for this
//view.layer.zPosition = 1
// .cornerRadius is not a property of `UIView`
//view.cornerRadius = 20
// assuming this is in a UIView extension
view.addShadow(ofColor: .lightGray, radius: 3, offset: .zero, opacity: 0.3)
view.translatesAutoresizingMaskIntoConstraints = false
// no such thing as alertView
//return alertView
return view
}()
However... if we assume you have code that actually works (labels are defined and created somewhere, subviews are added correctly, etc), the most likely reason you're not seeing the shadow is because your alertViewNew probably has a clear background. If it is clear, there is nothing there to "cast a shadow."
Try setting alertViewNew.backgroundColor = .white and see if that fixes the problem.
Or, try this working example:
class CustomAlertTestVC: UIViewController {
lazy var alertViewNew: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(alertViewNew)
setUpAlertView()
}
func setUpAlertView() {
let titleLabel = UILabel()
let descriptionLabel = UILabel()
let updateButton = UIButton()
titleLabel.font = .boldSystemFont(ofSize: 16.0)
titleLabel.text = "New Version Available"
descriptionLabel.font = .systemFont(ofSize: 16.0)
descriptionLabel.numberOfLines = 0
descriptionLabel.text = "Please, Update application to the new version to continue."
updateButton.setTitle("UPDATE", for: [])
updateButton.setTitleColor(.systemBlue, for: [])
[titleLabel, descriptionLabel, updateButton].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
alertViewNew.addSubview($0)
}
NSLayoutConstraint.activate([
alertViewNew.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// no need for centerX since we're adding leading and trailing constraints
//alertViewNew.centerXAnchor.constraint(equalTo: view.centerXAnchor),
alertViewNew.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
alertViewNew.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
titleLabel.leadingAnchor.constraint(equalTo: alertViewNew.leadingAnchor, constant: 20),
titleLabel.trailingAnchor.constraint(equalTo: alertViewNew.trailingAnchor, constant: -20),
titleLabel.topAnchor.constraint(equalTo: alertViewNew.topAnchor, constant: 20),
descriptionLabel.leadingAnchor.constraint(equalTo: alertViewNew.leadingAnchor, constant: 20),
descriptionLabel.trailingAnchor.constraint(equalTo: alertViewNew.trailingAnchor, constant: -20),
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
updateButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 5),
updateButton.trailingAnchor.constraint(equalTo: alertViewNew.trailingAnchor, constant: -20),
// really no need for width constraint
//updateButton.widthAnchor.constraint(equalToConstant: 65),
updateButton.bottomAnchor.constraint(equalTo: alertViewNew.bottomAnchor, constant: -20),
])
alertViewNew.layer.shadowColor = UIColor.lightGray.cgColor
alertViewNew.layer.shadowOffset = .zero
alertViewNew.layer.shadowRadius = 3.0
alertViewNew.layer.shadowOpacity = 0.3
alertViewNew.layer.cornerRadius = 20.0
// to get the view's layer to "cast a shadow"
// either set the view's backgroundColor
alertViewNew.backgroundColor = .white
// or, set the layer's backgroundColor
//alertViewNew.layer.backgroundColor = UIColor.white.cgColor
}
}
Output:

More complicated AutoLayout equations

Hi Please take a look at the following mockup:
I wanted to know how I can create the constraint from above:
V2.top = C1.top + n * V1.height
Because this is not something like the default equation for constraints:
item1.attribute1 = multiplier × item2.attribute2 + constant
I know I can just use AutoResizingMask but it will create a real mess in my code because my code is very complicated, and I also don't like AutoResizingMask that much.
(by the way, please answer in Swift only!)
Thank you
You can do this with a UILayoutGuide -- from Apple's docs:
The UILayoutGuide class is designed to perform all the tasks previously performed by dummy views, but to do it in a safer, more efficient manner.
To get your desired layout, we can:
add a layout guide to C1
constrain its Top to C1 Top
constrain its Height to V1 Height with a "n" multiplier
constrain V2 Top to the guide's Bottom
Here is a complete example to demonstrate:
class GuideViewController: UIViewController {
// a label on each side so we can
// "tap to change" v1 Height and "n" multiplier
let labelN = UILabel()
let labelH = UILabel()
let containerView = UIView()
let v1 = UILabel()
let v2 = UILabel()
// a layout guide for v2's Top spacing
let layG = UILayoutGuide()
// we'll change these on taps
var n:CGFloat = 0
var v1H: CGFloat = 30
// constraints we'll want to modify when "n" or "v1H" change
var v1HeightConstraint: NSLayoutConstraint!
var layGHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
v1.text = "V1"
v2.text = "V2"
v1.textAlignment = .center
v2.textAlignment = .center
containerView.backgroundColor = .systemTeal
v1.backgroundColor = .green
v2.backgroundColor = .yellow
[containerView, v1, v2].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
containerView.addSubview(v1)
containerView.addSubview(v2)
view.addSubview(containerView)
// add the layout guide to containerView
containerView.addLayoutGuide(layG)
// respect safe area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's give the container 80-pts Top/Bottom and 120-pts on each side
containerView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 80.0),
containerView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 120.0),
containerView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -120.0),
containerView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -80.0),
// v1 Leading / Trailing / Bottom 20-pts
v1.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
v1.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
v1.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),
// just use v2's intrinisic height
// v2 Leading / Trailing 20-pts
v2.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
v2.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
// layout Guide Top / Leading / Trailing
layG.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
layG.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0.0),
layG.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0.0),
// and constrain v2 Top to layout Guide Bottom
v2.topAnchor.constraint(equalTo: layG.bottomAnchor, constant: 0.0),
])
// layout Guide Height equals v1 Height x n
layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
layGHeightConstraint.isActive = true
// v1 Height
v1HeightConstraint = v1.heightAnchor.constraint(equalToConstant: v1H)
v1HeightConstraint.isActive = true
// "tap to change" labels
[labelN, labelH].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
$0.textAlignment = .center
$0.numberOfLines = 0
view.addSubview($0)
let t = UITapGestureRecognizer(target: self, action: #selector(tapHandler(_:)))
$0.addGestureRecognizer(t)
$0.isUserInteractionEnabled = true
}
NSLayoutConstraint.activate([
labelN.topAnchor.constraint(equalTo: containerView.topAnchor),
labelN.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 8.0),
labelN.trailingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: -8.0),
labelN.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
labelH.topAnchor.constraint(equalTo: containerView.topAnchor),
labelH.leadingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 8.0),
labelH.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -8.0),
labelH.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
updateInfo()
}
#objc func tapHandler(_ gr: UITapGestureRecognizer) -> Void {
guard let v = gr.view else {
return
}
// if we tapped on the "cylcle N" label
if v == labelN {
n += 1
if n == 6 {
n = 0
}
// can't change multiplier directly, so
// de-Activate / set it / Activate
layGHeightConstraint.isActive = false
layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
layGHeightConstraint.isActive = true
}
// if we tapped on the "cylcle v1H" label
if v == labelH {
v1H += 5
if v1H > 50 {
v1H = 30
}
v1HeightConstraint.constant = v1H
}
updateInfo()
}
func updateInfo() -> Void {
var s: String = ""
s = "Tap to cycle \"n\" from Zero to 5\n\nn = \(n)"
labelN.text = s
s = "Tap to cycle \"v1H\" from 30 to 50\n\nv1H = \(v1H)"
labelH.text = s
}
}
When you run it, it will look like this:
Each time you tap the left side, it will cycle the n multiplier variable from Zero to 5, and update the constraints.
Each time you tap the right side, it will cycle the v1H height variable from 30 to 50, and update the constraints.
It can be solved by using a helper view.
A helper view is in this case just a UIView used for sizing purpose, without visible content of its own. Either set its alpha = 0 or hidden = true.
Set helperView.top = c1.top
Set helperView.height = v1.height
Set v2.top = helperView.bottom + 5
You also need to set the width and leading for the helper view, but their values are not important.

Programatically created UIPageControl not showing

In my view hierarchy, I have a UIPageViewController (inside a container view). Underneath that is the UIPageControl and at the bottom is a stack view consisting of a text view and a button. I see the UIPageViewController and the stack view but not the UIPageControl. Any idea what I am doing wrong:
// Page view controller
introPageViewC = IntroPageViewC()
addChild(introPageViewC)
introPageViewC.view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(introPageViewC.view)
NSLayoutConstraint.activate([
introPageViewC.view.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
introPageViewC.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0),
introPageViewC.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0.0),
introPageViewC.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0.0),
])
introPageViewC.didMove(toParent: self)
// Page view control
introPageControl.currentPageIndicatorTintColor = UIColor.orange
introPageControl.pageIndicatorTintColor = UIColor.lightGray.withAlphaComponent(0.8)
view.addSubview(introPageControl)
introPageControl.translatesAutoresizingMaskIntoConstraints = false
introPageControl.topAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 10).isActive = true
introPageControl.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Stack view
view.addSubview(stackView)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 30
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: introPageControl.bottomAnchor, constant: 10).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40).isActive = true
Expected output:
Edit:
Attempt at adding to the stack view:
var allViews = [UIView]()
// Adding to stackview
introPageControl.currentPageIndicatorTintColor = UIColor.orange
introPageControl.pageIndicatorTintColor = UIColor.lightGray.withAlphaComponent(0.8)
allViews.append(introPageControl)
nameTextField = UITextField(frame: CGRect(x: 0, y: 0, width: 120, height: 40))
nameTextField.placeholder = "Mealplan name"
Utilities.styleTextField(nameTextField)
nameTextField.setPadding()
allViews.append(nameTextField)
let nextButton = UIButton(type: .system)
nextButton.frame = CGRect(x: 20, y: 20, width: 100, height: 40)
nextButton.setTitle("Next", for: .normal)
nextButton.titleLabel?.textColor = .white
nextButton.titleLabel?.font = UIFont(name: "NexaBold", size: 16)
Utilities.styleDefaultButton(nextButton)
nextButton.addTarget(self, action: #selector(tapSubmit(_:)), for: .touchUpInside)
allViews.append(nextButton)
errorLabel.frame = CGRect(x: 20, y: 20, width: 100, height: 40)
errorLabel.font = UIFont(name: "NexaBold", size: 16)
errorLabel.textColor = .systemRed
errorLabel.textAlignment = .center
errorLabel.alpha = 0
allViews.append(errorLabel)
for eachView in allViews {
stackView.addArrangedSubview(eachView)
}
introPageControl.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
I believe you meant centerXAnchor?
I think you've got the topAnchor all wrong. Right now, it seems to be constrained to the bottom of the containerView plus 10, which is just off the screen. I think you meant to say -10.
introPageControl.topAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -10).isActive = true