How to update constraints based on user input - ios

I have a view controller view with 3 column sections (called legs, aView, bView, and cView) by default. The user can switch it between 2 and 3 in a settings menu that gets presented modally over the same screen.
I'm trying to get the columns to take up space equally on the screen, so when there are 3 columns, they each take up roughly 1/3 of the width (ignoring padding), and when there are 2, they each take up roughly 1/2.
My current method is setting some constraints that are always active, and then for the ones that change depending on the number of columns, using an if statement. I tried some variations of layoutIfNeeded(), removeConstraints, and others but not entirely sure how to implement them.
For some clarification on the code, xView is the column view, which contains xTitle and xTextView. There is also a mainButton above the text views, and button1 below the text views. This all exists inside a contentView and scrollView setup.
let sidePadding: CGFloat = 15
func placeViews() { // run in viewWillAppear
let alwaysConstraints = [
aView.topAnchor.constraint(equalTo: mainButton.bottomAnchor, constant: 25),
aView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: sidePadding),
aView.rightAnchor.constraint(equalTo: bView.leftAnchor),
bView.topAnchor.constraint(equalTo: aView.topAnchor),
bView.bottomAnchor.constraint(equalTo: aView.bottomAnchor),
aTitle.topAnchor.constraint(equalTo: aView.topAnchor),
bTitle.topAnchor.constraint(equalTo: aTitle.topAnchor),
aTitle.centerXAnchor.constraint(equalTo: aView.centerXAnchor),
bTitle.centerXAnchor.constraint(equalTo: bView.centerXAnchor),
aTextView.topAnchor.constraint(equalTo: aTitle.bottomAnchor, constant: 25),
bTextView.topAnchor.constraint(equalTo: aTextView.topAnchor),
aTextView.bottomAnchor.constraint(equalTo: aView.bottomAnchor),
bTextView.bottomAnchor.constraint(equalTo: aTextView.bottomAnchor),
aTextView.centerXAnchor.constraint(equalTo: aView.centerXAnchor),
bTextView.centerXAnchor.constraint(equalTo: bView.centerXAnchor),
aTextView.widthAnchor.constraint(equalTo: aView.widthAnchor),
bTextView.widthAnchor.constraint(equalTo: bView.widthAnchor),
bView.widthAnchor.constraint(equalTo: aView.widthAnchor),
// Restrict buttons to leg views
button1.heightAnchor.constraint(equalToConstant: buttonHeight),
button1.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20),
button1.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20),
button1.topAnchor.constraint(equalTo: aView.bottomAnchor, constant: 40),
// Restrict button to bottom
button1.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
]
let twoLegConstraints = [
aView.widthAnchor.constraint(equalToConstant: (view.frame.width - (sidePadding * 2) / 2)),
bView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -sidePadding),
]
let threeLegConstraints = [
aView.widthAnchor.constraint(equalToConstant: (view.frame.width - (sidePadding * 2)) / 3),
bView.rightAnchor.constraint(equalTo: cView.leftAnchor),
cView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -sidePadding),
cView.topAnchor.constraint(equalTo: aView.topAnchor),
cView.bottomAnchor.constraint(equalTo: aView.bottomAnchor),
cTitle.topAnchor.constraint(equalTo: aTitle.topAnchor),
cTitle.centerXAnchor.constraint(equalTo: cView.centerXAnchor),
cTextView.topAnchor.constraint(equalTo: aTextView.topAnchor),
cTextView.bottomAnchor.constraint(equalTo: aTextView.bottomAnchor),
cTextView.centerXAnchor.constraint(equalTo: cView.centerXAnchor),
cTextView.widthAnchor.constraint(equalTo: cView.widthAnchor),
cView.widthAnchor.constraint(equalTo: aView.widthAnchor),
]
NSLayoutConstraint.activate(alwaysConstraints)
if legs == 2 {
cView.isHidden = true
NSLayoutConstraint.activate(twoLegConstraints)
} else if legs == 3 {
cView.isHidden = false
NSLayoutConstraint.activate(threeLegConstraints)
}
}
At the moment, the screen starts with 3 by default, and it works perfectly (each ~1/3 screen width). Then the user can switch it to 2, and it works perfectly again (each ~1/2 screen width). Then switching back to 3, the first two don't change at all, and the third column appears on the right edge of the screen.

Use a stack view with distribution = .fillEqually. It will take care of the layout and constraints automatically when you set the isHidden property of any of your views.

Related

Swift iOS Layout Constraint. Using Constant that is proportional to view height programatically

I am laying out a view programatically in Swift for iOS, but struggling to get my constraint quite how I want it. This is what I currently have:
NSLayoutConstraint.activate([
Logo.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 30),
Logo.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -30),
Logo.heightAnchor.constraint(equalToConstant: 55),
Logo.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 150),
])
This is fine on large screens but as the screen gets smaller I want to close the space between the Logo and the label. Currently this is set to a fixed constant of 150. What I would like to do is use a multiplier here that is based on the view height (or something similar) but I can not figure that out. How should I define the constraint to do this? Thanks!
You can try
/* Play with percent as you need also you can check current
device type iphone/ipad and set it accordingly */
let height = UIScreen.main.bounds.height * 0.25
logo.topAnchor.constraint(equalTo: label.bottomAnchor, constant: height),

StackView constraints with UIViews

I'm trying to setup a stackview in the middle of the screen with a padding of 20 to the left an right. Inside, I want to place two custom UIViews, but I don't quit understand how to do it. I tried giving the UIViews their respective Height's and Width's but I got nothing.
I believe the stack view has all the correct constraints. Here's the code:
func setupTeamViews() {
view.addSubview(teamsStackView)
teamsStackView.distribution = .fill
teamsStackView.axis = .vertical
teamsStackView.spacing = 20
teamsStackView.alignment = .fill
NSLayoutConstraint.activate([
teamsStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
teamsStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
teamsStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
teamsStackView.heightAnchor.constraint(equalToConstant: 420)
])
let height = CGFloat((teamsStackView.frame.height / 2) - 20)
let width = CGFloat((teamsStackView.frame.width - 20))
firstTeamView = ATTeamView(width: width, height: height)
firstTeamView.changeColor(color: .lightBlue)
firstTeamView.setTeamName(name: "Tobias")
firstTeamView.setNewPoints(points: "0")
secondTeamView = ATTeamView(width: width, height: height)
secondTeamView.changeColor(color: .white)
secondTeamView.setTeamName(name: "Valen")
secondTeamView.setNewPoints(points: "0")
teamsStackView.addArrangedSubview(firstTeamView)
teamsStackView.addArrangedSubview(secondTeamView)
}
How does a stackview work with UIview's? As far I understand, UIViews don't have intrinsicContentSize, but I don't know how to deal with that.
In stead of doing it programmatically you can add two container views attach them to your view controller, and then active them with the alpha.
This will make constraints easier and you can still program anything extra you would like
myView1.alpha = 1 // activate
myView2.alpha = 0 // deactivate
My code wasn't working because I've forgotten to set teamsStackView.translatesAutoresizingMaskIntoConstraints = false

Issue With Overlapping UIViews and Interactivity with UIKit/Swift

I am building an interface in Swift and UIKit targeting iOS. All of my views are programmatically constructed. I am having an issue where a UIView that overlaps another UIView (but does not completely cover it) prevents any tap events from passing through. The layout looks like this:
The navbar at the bottom is a custom view and functions fine. Tapping those buttons changes the active view behind it (the current view is the TextView with the SQL syntax highlighting). Above it is a UILabel (with the text "Connected") and a UIView (the circle with the gradient background). I will refer to this element as the "ButtonView" and the label as the "StatusView". The "StatusView" is anchored to the navbar and the enclosing view, and the "ButtonView" is anchored to the navbar and the "StatusView".
This is the relevant layout code:
view.addSubview(navBar!)
view.addSubview(status)
view.addSubview(button)
status.translatesAutoresizingMaskIntoConstraints = false
status.layer.masksToBounds = true
status.text = "Example String"
status.textColor = .white
status.layer.cornerRadius = 20.0
status.layoutMargins = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
button.translatesAutoresizingMaskIntoConstraints = false
button.layer.masksToBounds = true
button.layer.cornerRadius = 35.0
// button.isUserInteractionEnabled = false
// button.backgroundColor = .white
NSLayoutConstraint.activate([
status.bottomAnchor.constraint(equalTo: navBar!.topAnchor, constant: -10.0),
status.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10.0),
status.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -90.0),
status.heightAnchor.constraint(equalToConstant: 50.0),
button.bottomAnchor.constraint(equalTo: navBar!.topAnchor, constant: -10.0),
button.leadingAnchor.constraint(equalTo: status.trailingAnchor, constant: 10.0),
button.widthAnchor.constraint(equalToConstant: 70.0),
button.heightAnchor.constraint(equalToConstant: 70.0)
])
The intended behavior is that even with the "ButtonView" present, the SQL view can still be tapped and edited. However, when the "ButtonView" is present, the SQL view can no longer be focused. If the "ButtonView"'s isUserInteractionEnabled property is set to false or the "ButtonView" is removed, everything behaves correctly. The "StatusView" does not seem to have any adverse effects, without having to edit any of its properties. (Note that there is no gestural behavior currently assigned to the "ButtonView", and that is not a UIButton). The debug view does not seem to show any overlapping layers:
Ideally, I would like to eventually add a gesture recognizer to this view, but wondering if that will forever disallow me from accessing the views that are further in the background? Is it possible to overlap interactive views like this, or am I somehow screwing up the responder chain?

setting textfield constraints to hold effective in screen sizes 4.5 in to 6.5 in

if I set fixed width it either appears too large for small screen (4.5 in) or too small for large screen (6.5 in)
and
is there any special way to ensure the constraints hold good in all
constraints
Like Jatin mentioned in the comments, you can use leading and trailing anchors relative to the view like this,
textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
textField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
Or, you could set the width as a multiplier to the width of the view.
textField.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.80).isActive = true
Note: Change the constant and multiplier values to suit your needs.

ios correct way to use constraintLessThanOrEqualToSystemSpacingAfter for trailingAnchor

I'd like to programmatically layout a UILabel that should fit the width of the screen, with the system spacing as left and right insets. Here's my code:
statusLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
statusLabel.numberOfLines = 0
statusLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(statusLabel)
statusLabel.topAnchor.constraint(equalTo: otherView.bottomAnchor, constant: 0),
statusLabel.leadingAnchor.constraintEqualToSystemSpacingAfter(view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1),
statusLabel.trailingAnchor.constraintLessThanOrEqualToSystemSpacingAfter(view.safeAreaLayoutGuide.trailingAnchor, multiplier: 1),
statusLabel.bottomAnchor.constraint(equalTo: someOtherView.topAnchor, constant: 0)
Here is the result:
the label is laid out using the system spacing as the left inset, as intended, but its trailingAnchor seems to be equal to the superview's trailingAnchor rather than adding a system spacing between the two.
I've tried using constraintEqualToSystemSpacingAfter and constraintGreaterThanOrEqualToSystemSpacingAfter but got the same results.
Any ideas on how to get the system spacing between the label and its superview's trailing anchors?
Reverse the order Like this
view.safeAreaLayoutGuide.trailingAnchor.constraintEqualToSystemSpacingAfter(statusLabel.trailingAnchor, multiplier: 1).isActive = true
view first & statusLabel next.

Resources