Reputation: 31
I've created a UIView, which has buttons in it. I set the constraints like this :
NSLayoutConstraint.activate([
sampleView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20),
sampleView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
sampleView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
])
Not added any height or bottom anchor as I want it to be dynamic in height. Initially sampleView has no buttons, so it doesn't appear in UI. I then add buttons in it vertical to each other and it works fine.
The problem I'm facing right now is, there is another view(tempView) just below this sampleView. And initially I set tempView's topAnchor to sampleView's bottomAnchor. So it is placed properly on screen initially.
When I add buttons in sampleView, it expands, but tempView don't. I tried with view.layoutIfNeeded() and setting the topAnchor with new height of sampleView but nothing works out for me.
Upvotes: 0
Views: 259
Reputation: 77672
You must do something to give sampleView
a Height.
One easy way is to add a vertical UIStackView
as a subview of sampleView
, constrain all 4 sides to sampleView
, and then add buttons as arranged subviews of the stack view.
Quick example:
class ExampleVC: UIViewController {
let topView = UIView()
let sampleView = UIView()
let tempView = UIView()
// let's use a vertical stack view to hold the buttons inside sampleView
let stackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
[topView, sampleView, tempView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
topView.backgroundColor = .yellow
sampleView.backgroundColor = .green
tempView.backgroundColor = .cyan
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
topView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
topView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
topView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
topView.heightAnchor.constraint(equalToConstant: 100.0),
sampleView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20.0),
sampleView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
sampleView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
// sampleView has no Height constraint ...
// we're adding a stack view to it, and as we add buttons to the stack view
// it will control the Height
tempView.topAnchor.constraint(equalTo: sampleView.bottomAnchor, constant: 20.0),
tempView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
tempView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
tempView.heightAnchor.constraint(equalToConstant: 100.0),
])
// stackView to hold the buttons
stackView.axis = .vertical
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
sampleView.addSubview(stackView)
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to sampleView
stackView.topAnchor.constraint(equalTo: sampleView.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: sampleView.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: sampleView.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: sampleView.bottomAnchor, constant: 0.0),
])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let btnNumber = stackView.arrangedSubviews.count + 1
let btn = UIButton()
btn.backgroundColor = .red
btn.setTitle("Button \(btnNumber)", for: [])
stackView.addArrangedSubview(btn)
}
}
At start, because we haven't added any buttons to the stack view, it will look like this:
Tap anywhere to add buttons -- here is what we get after the first tap:
and after a couple more taps:
Edit - quick example #2...
You didn't include any information about how you are arranging the buttons horizontal to each other if fitted in one line else new button will down to previous -- but here is one approach.
Add a variable Height constraint as a property to your controller class:
// this will be updated when we add buttons
var sampleViewHeightConstraint: NSLayoutConstraint!
We will initialize sampleViewHeightConstraint
as the .heightAnchor
of sampleView
, with a constant of Zero.
When we add button(s) to sampleView
, we will calculate where the new button should be placed (i.e. "wrapping to the next row" if we've reached the edge of the view).
We will then update sampleViewHeightConstraint.constant
based on the bottom of the last button added:
class Example2VC: UIViewController {
let topView = UIView()
let sampleView = UIView()
let tempView = UIView()
// this will be updated when we add buttons
var sampleViewHeightConstraint: NSLayoutConstraint!
let btnTitles: [String] = [
"here", "are some", "sample", "button", "titles",
"so we have", "varying width", "buttons",
"that will be arranged", "on rows fitting",
"inside", "this view",
]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
[topView, sampleView, tempView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
topView.backgroundColor = .yellow
sampleView.backgroundColor = .green
tempView.backgroundColor = .cyan
let g = view.safeAreaLayoutGuide
// initialize the sampleViewHeightConstraint
sampleViewHeightConstraint = sampleView.heightAnchor.constraint(equalToConstant: 0.0)
NSLayoutConstraint.activate([
topView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
topView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
topView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
topView.heightAnchor.constraint(equalToConstant: 100.0),
sampleView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20.0),
sampleView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
sampleView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
// sampleViewHeightConstraint will be updated when we add buttons
sampleViewHeightConstraint,
tempView.topAnchor.constraint(equalTo: sampleView.bottomAnchor, constant: 20.0),
tempView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
tempView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
tempView.heightAnchor.constraint(equalToConstant: 100.0),
])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let thisTitle: String = btnTitles[sampleView.subviews.count % btnTitles.count]
let btn = UIButton()
btn.backgroundColor = .red
btn.setTitle(thisTitle, for: [])
let sz: CGSize = btn.intrinsicContentSize
// let's make the button frame a little wider than the title
btn.frame = .init(x: 0.0, y: 0.0, width: sz.width + 16.0, height: sz.height)
if sampleView.subviews.count == 0 {
// this is the first button we're adding,
// so put it at 8,8
btn.frame.origin = .init(x: 8.0, y: 8.0)
} else {
if let lastBtn = sampleView.subviews.last {
btn.frame.origin = .init(x: lastBtn.frame.maxX + 8.0, y: lastBtn.frame.minY)
if btn.frame.maxX > sampleView.frame.width - 8.0 {
// extends past the right edge, so "move it down a row"
btn.frame.origin = .init(x: 8.0, y: lastBtn.frame.maxY + 8.0)
}
}
}
// add the new button
sampleView.addSubview(btn)
// update the sampleViewHeightConstraint
if let lastBtn = sampleView.subviews.last {
sampleViewHeightConstraint.constant = lastBtn.frame.maxY + 8.0
}
}
}
as with the first example, it starts out like this:
we tap anywhere to add a button:
tap a few times to add more buttons:
the next button will exceed the view width, so we "wrap" it:
another tap:
and after a bunch of taps:
However you are adding the buttons and arranging them, the key point is that you must do something to give sampleView
a height... updating the .constant
value of a constraint as shown in this example is one way to do so.
Upvotes: 1