To design and create my UI, I always use auto layouts and do it programmatically instead of using storyboard.
In every view
class of mine, I have a method called
private func setupView(frame:CGRect) {
/* START CONTAINER VIEW */
containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: frame.width * (13 / IPHONE8_SCREEN_WIDTH)).isActive = true
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -frame.width * (13 / IPHONE8_SCREEN_WIDTH)).isActive = true
containerView.topAnchor.constraint(equalTo: topAnchor, constant: frame.height * (26 / IPHONE8_SCREEN_HEIGHT)).isActive = true
containerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
containerView.backgroundColor = UIColor.white
/* END CONTAINER VIEW */
...
}
to initialize the components. Now let’s say, in the method above, I initialize 10 UI components which are properly displayed when I run my code. However, depending on some variables, I have another function that is being called
private func addNextRoundInformation() {
..
nextRoundLabel = UILabel()
nextRoundLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(nextRoundLabel)
nextRoundLabel.leadingAnchor.constraint(equalTo: currentRoundLabel.leadingAnchor).isActive = true
nextRoundLabel.widthAnchor.constraint(equalTo:currentRoundLabel.widthAnchor).isActive = true
nextRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
}
which should place a new label between some others which were already initialized.
Of course, when putting the new label between some particular ones, I also update the auto layout constraints of the of the bottom label like
private func updateNumberOfWinnersLabelConstraint() {
numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: nextRoundLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: nextRoundLabelValue.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
}
The topAnchor of each label depends on the bottom anchor of the previous one.
With this approach, I can’t see nextRoundLabel at all. It only appears, if I initialize it in the private func setupView(frame:CGRect) {}
Why?
2
Answers
I found a working solution:
First, I declared two helper variables
then I made some minor adjustments in my setupView function:
By introducing the helper variables, I could easily deactivate the topConstraint when adding the nextRoundLabel
Basically, I only had to update the topConstraints of the numberOfWinnersPerRoundLabel and numberOfWinnersPerRoundLabelValue since everything else would be the same. No changes needed for currentRoundLabel.
I tested it and it worked!
You could do this with "Top-to-Bottom" constraints, but it would be rather complex.
You would need to essentially create a "Linked List" to track each view, the views above and below it, and its constraints.
So, to "insert" a new view after the 3rd view, you would need to:
Putting your views in a
UIStackView
turns that process into a single line of code:Here’s a quick example:
It starts looking like this:
after tapping the "Insert" button, it looks like this:
Edit
To explain why your current approach isn’t working…
Starting with this layout:
each label’s Top is constrained to the previous label’s Bottom (with
constant: spacing
).Those constraints are indicated by the blue arrows.
You then want to "insert"
Next Round Label
betweenRound Ends In
andWinners Per Round
:Your code:
Next Round Label
to the Bottom ofRound Ends In
Winners Per Round
to the Bottom ofNext Round Label
but…
Winners Per Round
already has a.topAnchor
connected toRound Ends In
, so it now has two top anchors.The conflicting constraints are shown in red:
As I said, I think your description of what you’re trying to do would lend itself to using stack views, and would make "inserting" views so much easier.
But, if you need to stick with your current "Top-to-Bottom" constraints approach, you have several options.
One – remove all the labels, then re-add and re-constrain them, including the one you’re inserting.
Two – track the constraints (using an array or custom object properties) so you can deactivate the conflicting constraint(s).
Three – use some code along the lines of
to "find" the constraint that needs to be deactivated.