Resize constraints for UIView. swift - ios

I have two custom UIViews on the screen. UIViewOne occupies 75% of the screen, and UIViewTwo 25%. I need to click on UIViewTwo, resize it to make the second bigger and smaller first.
I also know that this needs to be done using constraints, but I don't know how. Please tell me how to solve this problem.

For both view1 and view2,
add constraint as equal height to superview
update the height constraint multiplier to 0.75 for view1 and 0.25 for view2.
When you click on view2, similarly update the height constraint multiplier to 0.25 for view1 and 0.75 for view2.

One approach is to add two Height constraints to view1 and change their Priority,
add a Height constraint at 75% (multiplier = 0.75)
set its Priority to 999
add a Height constraint at 25% (multiplier = 0.25)
set its Priority to 998
constraint the Top of view2 to the bottom of view1.
At the start, the 75% Height constraint will have priority over the 25% constraint ... 999 is greater than 998. When you tap view2, change the 75% constraint's Priority to 997. Now 997 is less than 998, so the 25% constraint gets the priority.
Since view2's top is constrained to view1's bottom, view2 will automatically resize.
Here is an example you can run as-is (just assign it to a view controller... no IBOutlet or IBAction connections needed):
class PercentViewController: UIViewController {
let view1: UIView = {
let v = UIView()
v.backgroundColor = .red
return v
}()
let view2: UIView = {
let v = UIView()
v.backgroundColor = .green
return v
}()
var topView75: NSLayoutConstraint!
var topView25: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// we're using auto-layout
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
// add views
view.addSubview(view1)
view.addSubview(view2)
// respect safe area
let g = view.safeAreaLayoutGuide
// create "75% height constraint"
topView75 = view1.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.75)
// create "25% height constraint"
topView25 = view1.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.25)
// give 75% constraint higher priority than 25% constraint
topView75.priority = UILayoutPriority(rawValue: 999)
topView25.priority = UILayoutPriority(rawValue: 998)
NSLayoutConstraint.activate([
// view1 constrained Top, Leading, Trailing (to safe-area)
view1.topAnchor.constraint(equalTo: g.topAnchor),
view1.leadingAnchor.constraint(equalTo: g.leadingAnchor),
view1.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// view2 constrained Bottom, Leading, Trailing (to safe-area)
view2.bottomAnchor.constraint(equalTo: g.bottomAnchor),
view2.leadingAnchor.constraint(equalTo: g.leadingAnchor),
view2.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// view2 Top constrained to view1 Bottom
view2.topAnchor.constraint(equalTo: view1.bottomAnchor),
// activate both Height constraints
topView75,
topView25,
])
// create tap gesture recognizers
let tap1 = UITapGestureRecognizer(target: self, action: #selector(view1Tapped(_:)))
let tap2 = UITapGestureRecognizer(target: self, action: #selector(view2Tapped(_:)))
// add to the views
view1.addGestureRecognizer(tap1)
view2.addGestureRecognizer(tap2)
}
#objc func view1Tapped(_ sender: Any) -> Void {
// view1 tapped, so give 75% constraint a higher priority than 25% constraint
topView75.priority = UILayoutPriority(rawValue: 999)
// 0.3-second animation
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
#objc func view2Tapped(_ sender: Any) -> Void {
// view2 tapped, so give 25% constraint a higher priority than 75% constraint
topView75.priority = UILayoutPriority(rawValue: 997)
// 0.3-second animation
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}

Related

iOS: Programatic ScrollView not scrolling (contentSize set)

I have created a scrollView in code and added several labels to it:
private let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .systemTeal
return scrollView
}()
In viewDidLoad:
view.addSubview(scrollView)
[view1, label1, label2, label3, label4, label5].forEach { scrollView.addSubview($0) }
setViewContraints()
Then in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width: view.width, height: view.height + 300)
}
The first view is anchored to the scrollview safeAreaLayoutGuide topAnchor, then each subsequent label's topAnchor is anchored to the bottom of the previous bottomAnchor. Every view's trailing and leading anchors are anchored to the scrollview's trailing and leading anchors. This is done in setViewConstraints().
view.width and view.height return view.frame.size.width and view.frame.size.height respectively.
All the content appears fine but the scrollView doesn't work. What am I doing wrong?
I realized I needed to set the bottomAnchor of my bottom-most view to the bottom anchor of the scroll view. That did the trick.

Fitting UIStackView in UIScrollView programmatically

I added a stackView to a scrollView. I set the width, X, and Y constraints of the scrollView same as main view, with a fixed height of 50. For the stack constraints, I did the same thing but relative to the scrollView instead of the view.
My issue is when I add UIImageViews to my stack (all images are 50 x 50). I need the stack to show only the first three UIImageViews, and scroll horizontally if there are more than 3. So far, my stack always shows all the UIImageViews.
Any suggestion is appreciated. Been working on this for 2 days now. THANKS!
What you probably want to do...
constrain all 4 sides of the stack view to the scroll view's Content Layout Guide
constrain the Height of the stack view equal to the Height of the scroll view's Frame Layout Guide
do NOT constrain the Width of the stack view
set the stack view's Distribution to Fill
Create a "tab view" - here's an example with a 50 x 50 centered image view, rounded top corners and a 1-pt outline:
We can create that with this simple class:
class MyTabView: UIView {
let imgView = UIImageView()
init(with image: UIImage) {
super.init(frame: .zero)
imgView.image = image
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
imgView.translatesAutoresizingMaskIntoConstraints = false
// light gray background
backgroundColor = UIColor(white: 0.9, alpha: 1.0)
addSubview(imgView)
NSLayoutConstraint.activate([
// centered
imgView.centerXAnchor.constraint(equalTo: centerXAnchor),
imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
// 50x50
imgView.widthAnchor.constraint(equalToConstant: 50.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
])
// a little "styling" for the "tab"
clipsToBounds = true
layer.cornerRadius = 12
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
layer.borderWidth = 1
layer.borderColor = UIColor.darkGray.cgColor
}
}
For each "tab" that we add to the stack view, we'll set its Width constraint equal to the scroll view's Frame Layout Guide widthAnchor with multiplier: 1.0 / 3.0. That way each "tab view" will be 1/3rd the width of the scroll view:
With 1, 2 or 3 "tabs" there will be no horizontal scrolling, because they all fit within the frame.
Once we have more than 3 "tabs" the stack view's width will exceed the width of the frame, and we'll have horizontal scrolling:
Here's the view controller I used for that. It creates 9 "tab images"... starts with a single "tab"... each tap will ADD a "tab" until we have all 9, at which point each tap will REMOVE a "tab":
class StackAsTabsViewController: UIViewController {
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.distribution = .fill
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// a label to show what's going on
let statusLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// array to hold our "tab" images
var images: [UIImage] = []
// we'll add a "tab" on each tap
// until we reach the end of the images array
// then we'll remove a "tab" on each tap
// until we're back to a single "tab"
var isAdding: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// add the "status" label
view.addSubview(statusLabel)
// add stackView to scrollView
scrollView.addSubview(stackView)
// add scrollView to view
view.addSubview(scrollView)
// respect safe area
let g = view.safeAreaLayoutGuide
// scrollView Content and Frame Layout Guides
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView Top / Leading / Trailing
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// height = 58 (image will be 50x50, so a little top and bottom padding)
scrollView.heightAnchor.constraint(equalToConstant: 58.0),
// constrain stackView all 4 sides to scrollView Content Layout Guide
stackView.topAnchor.constraint(equalTo: contentG.topAnchor),
stackView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
// stackView Height equal to scrollView Frame Height
stackView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
// statusLabel in the middle of the view
statusLabel.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 40.0),
statusLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
statusLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0)
])
// let's create 9 images using SF Symbols
for i in 1...9 {
guard let img = UIImage(systemName: "\(i).circle.fill") else {
fatalError("Could not create images!!!")
}
images.append(img)
}
// add the first "tab view"
self.updateTabs()
// tap anywhere in the view
let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
view.addGestureRecognizer(t)
}
#objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
updateTabs()
}
func updateTabs() -> Void {
if isAdding {
// get the next image from the array
let img = images[stackView.arrangedSubviews.count]
// create a "tab view"
let tab = MyTabView(with: img)
// add it to the stackView
stackView.addArrangedSubview(tab)
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// each "tab view" is 1/3rd the width of the scroll view frame
tab.widthAnchor.constraint(equalTo: frameG.widthAnchor, multiplier: 1.0 / 3.0),
// each "tab view" is the same height as the scroll view frame
tab.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
} else {
stackView.arrangedSubviews.last?.removeFromSuperview()
}
if stackView.arrangedSubviews.count == 1 {
isAdding = true
} else if stackView.arrangedSubviews.count == images.count {
isAdding = false
}
updateStatusLabel()
}
func updateStatusLabel() -> Void {
// we'll do this async, to make sure the views have been updated
DispatchQueue.main.async {
let numTabs = self.stackView.arrangedSubviews.count
var str = ""
if self.isAdding {
str += "Tap anywhere to ADD a tab"
} else {
str += "Tap anywhere to REMOVE a tab"
}
str += "\n\n"
str += "Number of tabs: \(numTabs)"
str += "\n\n"
if numTabs > 3 {
str += "Tabs WILL scroll"
} else {
str += "Tabs will NOT scroll"
}
self.statusLabel.text = str
}
}
}
Play with it, and see if that's what you're going for.

Vertically rotated UIPageControl taking too much space

I used CGAffineTransform to rotate a horizontal UIPageControl vertical. But when I added it besides my collection view it's taking too much width. And when I add a width anchor on it, the UIPageControl disappears.
noticesPagingIndicator = UIPageControl()
let angle = CGFloat.pi/2
noticesPagingIndicator.transform = CGAffineTransform(rotationAngle: angle)
NSLayoutConstraint.activate([
// noticesPagingIndicator.widthAnchor.constraint(equalToConstant: 30),
noticesPagingIndicator.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
noticesPagingIndicator.centerYAnchor.constraint(equalTo: noticesCollectionView.centerYAnchor),
noticesCollectionView.leadingAnchor.constraint(equalTo: noticesPagingIndicator.trailingAnchor),
noticesCollectionView.topAnchor.constraint(equalTo: noticeStackView.bottomAnchor, constant: 8),
noticesCollectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
noticesCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
When I look at the UIView hierarchy, I see a lot of padding along the UIPageControl
With the width anchor enabled:
Get to know the Debug View Hierarchy tool. It can help you figure out most layout issues.
When you transform a view, that doesn't change its bounds and thus doesn't change its constraint relationships to other UI elements.
With this code (8 pages, so 8 dots):
class ViewController: UIViewController {
let pgc = UIPageControl()
let greenLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
pgc.translatesAutoresizingMaskIntoConstraints = false
greenLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pgc)
view.addSubview(greenLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// page control Leading to safe area Leading + 20, centerY
pgc.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
pgc.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
// constrain greenLabel Leading to page control trailing + 8 and centerY, safe area trailing -8
greenLabel.leadingAnchor.constraint(equalTo: pgc.trailingAnchor, constant: 8.0),
greenLabel.centerYAnchor.constraint(equalTo: pgc.centerYAnchor),
greenLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
])
// rotate the page control
let angle = CGFloat.pi/2
pgc.transform = CGAffineTransform(rotationAngle: angle)
pgc.backgroundColor = .systemBlue
greenLabel.backgroundColor = .green
pgc.numberOfPages = 8
greenLabel.numberOfLines = 0
greenLabel.text = "UIPageControl indicates the number of open pages in an application by displaying a dot for each open page. The dot that corresponds to the currently viewed page is highlighted. UIPageControl supports navigation by sending the delegate an event when a user taps to the right or to the left of the currently highlighted dot."
}
}
You get this output:
As you've seen, the Green Label Leading constraint to the page control Trailing Anchor shows the page control width matches what it would be without the rotation.
If you inspect the views with Debug View Hierarchy, you'll see the page control looks like this:
The frame is w: 27.5 h: 217 but the bounds is w: 217 h: 27.5.
To fix this, you need to embed the page control in a "holder" view, constrain the holder view's Height to the page control's Width and Width to Height. Then constrain your other elements to that "holder" view:
class ViewController: UIViewController {
let pgcHolder = UIView()
let pgc = UIPageControl()
let greenLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
pgcHolder.translatesAutoresizingMaskIntoConstraints = false
pgc.translatesAutoresizingMaskIntoConstraints = false
greenLabel.translatesAutoresizingMaskIntoConstraints = false
pgcHolder.addSubview(pgc)
view.addSubview(pgcHolder)
view.addSubview(greenLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// center page control in its "holder" view
pgc.centerXAnchor.constraint(equalTo: pgcHolder.centerXAnchor),
pgc.centerYAnchor.constraint(equalTo: pgcHolder.centerYAnchor),
// constrain holder view leading to view + 20, centerY
pgcHolder.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
pgcHolder.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
// constrain holder view WIDTH to page control HEIGHT
pgcHolder.widthAnchor.constraint(equalTo: pgc.heightAnchor),
// constrain holder view HEIGHT to page control WIDTH
pgcHolder.heightAnchor.constraint(equalTo: pgc.widthAnchor),
// constrain greenLabel Leading to holder view trailing + 8 and centerY, safe area trailing -8
greenLabel.leadingAnchor.constraint(equalTo: pgcHolder.trailingAnchor, constant: 8.0),
greenLabel.centerYAnchor.constraint(equalTo: pgcHolder.centerYAnchor),
greenLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
])
let angle = CGFloat.pi/2
pgc.transform = CGAffineTransform(rotationAngle: angle)
pgcHolder.backgroundColor = .systemRed
pgc.backgroundColor = .systemBlue
greenLabel.backgroundColor = .green
pgc.numberOfPages = 8
greenLabel.numberOfLines = 0
greenLabel.text = "UIPageControl indicates the number of open pages in an application by displaying a dot for each open page. The dot that corresponds to the currently viewed page is highlighted. UIPageControl supports navigation by sending the delegate an event when a user taps to the right or to the left of the currently highlighted dot."
}
}
Now we have our desired output:
#DonMag Answer with storyboard
Step 1 :
In View Controller, Drag UIVIew --> Name it (holderView)
Step 2 :
Drag Page Control in the holderView
Step 4:
Select the holder View - add Center Y Constraint , Trailing Constraint with Super View
Step 5:
Select the page Control View - add Center X , Center Y constraint
Step 6 :
From the View List on left panel , select both parent view and page control view , add Equal With and Equal Height constraint
Step 7:
Now select the Equal Width constraint and from left panel (properties of constraint) update superView Width to SuperView Height ,
Same for Height Constraint
Your Constraint should look like this

Remove trailing constraint with ib action- Swift

I have created a View Controller with a segmented controller at the top. When you tap the segmented controller it just acts as a button and changes whether the imageView inside of the controller is portrait mode or in landscape mode just by calling a function that changes it to the according dimensions.
My problem is that the way I made it change is I just added contraints to the imageView, but when changing to landscape mode the trailing constraint doesn't get removed.
And, this is the code to change the imageView to portrait (The imageView already has the top and bottom contraints on it) :
func portraitContraints() {
NSLayoutConstraint.activate ([
// the trailing contraint
imageView.trailingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.trailingAnchor, constant: -75), // this is the contraints that doesn't get removed
// the leading contraints
imageView.leadingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.leadingAnchor, constant: 75
])
// the aspect ratio contraints
imageView.addConstraint(NSLayoutConstraint(item: self.imageView as Any,attribute: .height,relatedBy: .equal,toItem: self.imageView,attribute: .width,multiplier: (4.0 / 3.0),constant: 0))
}
This is the code to change the imageView to landscape:
func landscapeContraints() {
NSLayoutConstraint.activate([
// the trailing contraints
imageView.trailingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.trailingAnchor, constant: 0),
// the leading contraint
imageView.leadingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.leadingAnchor, constant: 0)
])
// the aspect ratio contraint
imageView.addConstraint(NSLayoutConstraint(item: self.imageView as Any,attribute: .height,relatedBy: .equal,toItem: self.imageView,attribute: .width,multiplier: (9.0 / 16.0),constant: 0))
}
This code works like a charm, except the only problem is that the trailing constraint from the portrait mode will stay on the view (the -75 constant constraint).
Landscape looks like this (notice the right side constant is -75):
Portrait looks like this:
You can do this by adding both ratio constraints, with different priorities. Change the priorities to make the desired ratio "active".
And, create leading and trailing constraint vars, so you can change their constants.
Here's a quick example:
class ToggleConstraintsViewController: UIViewController {
let imageView = UIImageView()
var isPortrait: Bool = true
var portraitConstraint: NSLayoutConstraint!
var landscapeConstraint: NSLayoutConstraint!
var leadingConstraint: NSLayoutConstraint!
var trailingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
imageView.backgroundColor = .blue
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
let g = view.safeAreaLayoutGuide
portraitConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 4.0/3.0)
portraitConstraint.priority = .defaultHigh
landscapeConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 9.0/16.0)
landscapeConstraint.priority = .defaultLow
leadingConstraint = imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 75.0)
trailingConstraint = imageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -75.0)
NSLayoutConstraint.activate([
// y-position constraint does not change
imageView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// these will have their priorities changed when desired
portraitConstraint,
landscapeConstraint,
// these will have their constants changed when desired
leadingConstraint,
trailingConstraint,
])
let t = UITapGestureRecognizer(target: self, action: #selector(self.toggleOrientation(_:)))
view.addGestureRecognizer(t)
}
#objc func toggleOrientation(_ g: UITapGestureRecognizer) -> Void {
isPortrait.toggle()
portraitConstraint.priority = isPortrait ? .defaultHigh : .defaultLow
landscapeConstraint.priority = isPortrait ? .defaultLow : .defaultHigh
leadingConstraint.constant = isPortrait ? 75.0 : 0.0
trailingConstraint.constant = isPortrait ? -75.0 : 0.0
}
}
Each time you tap the view, the imageView's width:height ratio will change, and the leading/trailing constraint constants will change.
Try removing the old constraints before adding in the new ones. Here's Apples documentation on how to do that: https://developer.apple.com/documentation/uikit/uiview/1622593-removeconstraints

How to increase UIView height which contains UIStackView

I have a custom view which contains a label, label can have multiple line text. So i have added that label inside a UIStackView, now my StackView height is increasing but the custom view height doesn't increases. I haven't added bottom constraint on my StackView. What should I do so that my CustomView height also increases with the StackView.
let myView = Bundle.main.loadNibNamed("TestView", owner: nil, options: nil)![0] as! TestView
myView.lbl.text = "sdvhjvhsdjkvhsjkdvhsjdvhsdjkvhsdjkvhsdjkvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsdjvhsdjvhsdjvhsdjvhsdjvhsjdvhsdjvhsdjvhsjdvhsdjvhsjdvhsdjvhsdjvhsdjvhsjdv"
myView.lbl.sizeToFit()
myView.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.width, height: myView.frame.size.height)
myView.setNeedsLayout()
myView.layoutIfNeeded()
self.view.addSubview(myView)
I want to increase my custom view height as per my stackview height.
Please help.
Example of stackView constraints with its superview.
Also superview should not have constraints for its height.
You should set the top and bottom anchors of your custom view to be constrained to the top and bottom anchors of your stackview. As your stackView grows, it will push that bottom margin along. Here's a programmatic example:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private lazy var stackView = UIStackView()
private lazy var addLabelButton = UIButton(type: .system)
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let stackViewContainer = UIView(frame: view.bounds)
stackViewContainer.backgroundColor = .yellow
stackViewContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackViewContainer)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
addLabelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(addLabelButton)
stackViewContainer.addSubview(stackView)
NSLayoutConstraint.activate([
// Container constrained to three edges of its superview (fourth edge will grow as the stackview grows
stackViewContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackViewContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackViewContainer.topAnchor.constraint(equalTo: view.topAnchor),
// stackView constraints - stackView is constrained to the
// for corners of its contaier, with margins
{
// Stackview has a height of 0 when no arranged subviews have been added.
let heightConstraint = stackView.heightAnchor.constraint(equalToConstant: 0)
heightConstraint.priority = .defaultLow
return heightConstraint
}(),
stackView.topAnchor.constraint(equalTo: stackViewContainer.topAnchor, constant: 8),
stackView.leadingAnchor.constraint(equalTo: stackViewContainer.leadingAnchor, constant: 8),
stackView.trailingAnchor.constraint(equalTo: stackViewContainer.trailingAnchor, constant: -8),
stackView.bottomAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: -8),
// button constraints
addLabelButton.topAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: 8),
addLabelButton.centerXAnchor.constraint(equalTo: stackViewContainer.centerXAnchor)
])
addLabelButton.setTitle("New Label", for: .normal)
addLabelButton.addTarget(self, action: #selector(addLabel(sender:)), for: .touchUpInside)
self.view = view
}
private(set) var labelCount = 0
#objc func addLabel(sender: AnyObject?) {
let label = UILabel()
label.text = "Label #\(labelCount)"
labelCount += 1
stackView.addArrangedSubview(label)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Note that when the UIStackView is empty, its height is not well defined. That is why I set its heightAnchor constraint to 0 with a low priority.
First of all you should add bottom constraint on your UIStackView. This will help auto layout in determining the run time size of UIStackView.
Now create instance of your custom UIView but do not set it's frame and add it to UIStackView. Make sure you Custom UiView has all the constraints set for auto layout to determine it's run time frame.
This will increase height of both UIView and UIStackView based on content of UIView elements.
For more details you can follow my detailed answer on this at https://stackoverflow.com/a/57954517/3339966

Resources