Swift - Autolayout constraints UIView-Encapsulated-Layout-Width - ios

I'm trying to do UI programatically and I'm getting weird constraint errors, but when I run the app, it it looks as expected.
What I'm trying to do:
I have a ViewController TodayVC where I have a UIView and I'm trying to render content of MWStepsActivityVC in that view.
Here is my TodayVC:
class TodayVC: UIViewController {
let scrollView = UIScrollView()
let contentView = UIView()
let stepsActivityView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
configureViewController()
configureScrollView()
configureContainerViews()
layoutUI()
}
func configureViewController() {
view.backgroundColor = .systemBackground
}
func configureScrollView() {
view.addSubview(scrollView)
scrollView.addSubview(contentView)
scrollView.pinToEdges(of: view)
contentView.pinToEdges(of: scrollView)
NSLayoutConstraint.activate([
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.heightAnchor.constraint(equalToConstant: 600)
])
}
func configureContainerViews() {
self.add(childVC: MWStepsActivityVC(activityType: .steps), to: self.stepsActivityView)
}
func layoutUI() {
contentView.addSubview(stepsActivityView)
stepsActivityView.translatesAutoresizingMaskIntoConstraints = false
let padding: CGFloat = 20
let itemHeight: CGFloat = 140
NSLayoutConstraint.activate([
stepsActivityView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding),
stepsActivityView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -padding),
stepsActivityView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
stepsActivityView.heightAnchor.constraint(equalToConstant: itemHeight),
])
print("TodayVC view width \(view.frame.size.width)")
}
func add(childVC: UIViewController, to containerView: UIView) {
addChild(childVC)
containerView.addSubview(childVC.view)
childVC.view.frame = containerView.bounds
childVC.didMove(toParent: self)
}
}
UIViewExtension:
extension UIView {
func addSubviews(_ views: UIView...) {
for view in views {
addSubview(view)
}
}
func pinToEdges(of superview: UIView) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: superview.topAnchor),
leadingAnchor.constraint(equalTo: superview.leadingAnchor),
trailingAnchor.constraint(equalTo: superview.trailingAnchor),
bottomAnchor.constraint(equalTo: superview.bottomAnchor),
])
}
}
MWStepsActivityVC:
class MWActivityVC: UIViewController {
let iconImageView = UIImageView()
let titleLabel = UILabel()
let counterLabel = UILabel()
init(activityType: ActivityType) {
super.init(nibName: nil, bundle: nil)
self.set(activityType: activityType)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
configureBackgroundView()
layoutUI()
placeholderData()
}
fileprivate func placeholderData() {
iconImageView.image = SFSymbols.steps
titleLabel.text = "StepsStepsStepsStepsStepsStepsStepsStepsStepsStepsStepsStepsStepsStepsStepsStepsStepsSteps"
counterLabel.text = "9000"
counterLabel.backgroundColor = .red
}
fileprivate func configureBackgroundView() {
view.layer.cornerRadius = 18
}
fileprivate func layoutUI() {
view.addSubviews(iconImageView, titleLabel, counterLabel)
iconImageView.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
counterLabel.translatesAutoresizingMaskIntoConstraints = false
let padding: CGFloat = 20
NSLayoutConstraint.activate([
iconImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: padding),
iconImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
iconImageView.heightAnchor.constraint(equalToConstant: 20),
iconImageView.widthAnchor.constraint(equalToConstant: 20),
titleLabel.centerYAnchor.constraint(equalTo: iconImageView.centerYAnchor),
titleLabel.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: padding),
titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
titleLabel.heightAnchor.constraint(equalToConstant: 20),
counterLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -padding),
counterLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
counterLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
counterLabel.heightAnchor.constraint(equalToConstant: 40)
])
print("MWStepsActivityVC view width \(view.frame.size.width)")
}
}
I'm getting these errors:
"<NSLayoutConstraint:0x600002228320 H:|-(20)-[UIImageView:0x7fc0abe077a0] (active, names: '|':UIView:0x7fc0abd10850 )>",
"<NSLayoutConstraint:0x6000022283c0 UIImageView:0x7fc0abe077a0.width == 20 (active)>",
"<NSLayoutConstraint:0x600002228460 H:[UIImageView:0x7fc0abe077a0]-(20)-[UILabel:0x7fc0abe08a70] (active)>",
"<NSLayoutConstraint:0x6000022284b0 UILabel:0x7fc0abe08a70.trailing == UIView:0x7fc0abd10850.trailing - 20 (active)>",
"<NSLayoutConstraint:0x600002230b90 'UIView-Encapsulated-Layout-Width' UIView:0x7fc0abd10850.width == 0 (active)>",
"<NSLayoutConstraint:0x600002228320 H:|-(20)-[UIImageView:0x7fc0abe077a0] (active, names: '|':UIView:0x7fc0abd10850 )>",
"<NSLayoutConstraint:0x600002228460 H:[UIImageView:0x7fc0abe077a0]-(20)-[UILabel:0x7fc0abe08a70] (active)>",
"<NSLayoutConstraint:0x6000022284b0 UILabel:0x7fc0abe08a70.trailing == UIView:0x7fc0abd10850.trailing - 20 (active)>",
"<NSLayoutConstraint:0x600002230b90 'UIView-Encapsulated-Layout-Width' UIView:0x7fc0abd10850.width == 0 (active)>",
"<NSLayoutConstraint:0x6000022285a0 H:|-(20)-[UILabel:0x7fc0abe08ce0] (active, names: '|':UIView:0x7fc0abd10850 )>",
"<NSLayoutConstraint:0x6000022285f0 UILabel:0x7fc0abe08ce0.trailing == UIView:0x7fc0abd10850.trailing - 20 (active)>",
"<NSLayoutConstraint:0x600002230b90 'UIView-Encapsulated-Layout-Width' UIView:0x7fc0abd10850.width == 0 (active)>"
I tried to use the wtfautolayout.com website to understand what's wrong, and it seems the issue is with UIView-Encapsulated-Layout-Width it thinks it's set to 0, but when I try to print view.frame.size.width I get correct width:
MWStepsActivityVC view width 375.0
TodayVC view width 375.0
Can someone help me with it? I'm struggling with this for a day already.

Finally I was able to fix it. I forgot to run configureContainerViews on main thread...
DispatchQueue.main.async {
self.configureContainerViews()
}

Related

Unable to simultaneously satisfy constraints when adding any non-zero constraint programmatically

import UIKit
#IBDesignable
class LargeButtonWithIcon: UIView {
var iconBackgroundView: UIView?
var iconIv: UIImageView?
#IBInspectable var iconImage: UIImage? {
didSet {
self.iconIv?.image = iconImage
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initializeView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
initializeView()
}
private func initializeView() {
initializeIconView()
}
private func initializeIconView() {
iconBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.height, height: self.frame.height))
addSubview(iconBackgroundView!)
iconBackgroundView?.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
iconBackgroundView?.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
iconBackgroundView?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
iconIv = UIImageView()
iconIv?.image = iconImage
iconIv?.clipsToBounds = true
iconIv?.contentMode = .scaleAspectFit
iconIv?.translatesAutoresizingMaskIntoConstraints = false
iconBackgroundView!.addSubview(iconIv!)
iconBackgroundView?.leadingAnchor.constraint(equalTo: iconBackgroundView!.leadingAnchor, constant: 20).isActive = true
// iconBackgroundView?.topAnchor.constraint(equalTo: iconBackgroundView!.topAnchor, constant: 0).isActive = true
// iconBackgroundView?.rightAnchor.constraint(equalTo: iconBackgroundView!.rightAnchor, constant: 20).isActive = true
// iconBackgroundView?.bottomAnchor.constraint(equalTo: iconBackgroundView!.bottomAnchor, constant: 20).isActive = true
// iconBackgroundView?.centerXAnchor.constraint(equalTo: iconBackgroundView!.centerXAnchor).isActive = true
// iconBackgroundView?.centerYAnchor.constraint(equalTo: iconBackgroundView!.centerYAnchor).isActive = true
}
}
I'm trying to achieve the following:
iconBackgroundView is the square at the left of the button, iconIv is the icon in that square.
When I activate any non-zero constraint for the iconIv (or all of them), it prints the warning:
2020-06-03 01:26:37.163392+0300 FindDifferences[5310:34823037] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x6000012bc6e0 UIView:0x7f880c0075a0.leading == UIView:0x7f880c0075a0.leading + 20 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000012bc6e0 UIView:0x7f880c0075a0.leading == UIView:0x7f880c0075a0.leading + 20 (active)>
But if I change the its constant to 0, it does not. Why, and how should I center the iconIv in this view with given padding?
This line doesn't make sense because you're trying to set a view's leading anchor equal to its own leading anchor.
iconBackgroundView?.leadingAnchor.constraint(equalTo: iconBackgroundView!.leadingAnchor, constant: 20).isActive = true
I think what you actually want is this:
iconIv?.leadingAnchor.constraint(equalTo: iconBackgroundView!.leadingAnchor, constant: 20).isActive = true

UITableViewCell not getting the dynamic cell size based on the icon inside it

I am programmatically adding tableviewcell and its contents. It has 2 items in it. a label and an imageview. The thing is, I am trying to set the icon height and it breaks the constraints. It shows up fine in the simulator, but I am trying to figure out why it breaks the constraints. Here is the code:
override func viewDidLoad() {
super.viewDidLoad()
setTitle(title: menu.text)
tableView.register(ListMenuCell.self, forCellReuseIdentifier: "listMenuCell")
tableView.estimatedRowHeight = 150
tableView.rowHeight = UITableView.automaticDimension
setupTableView()
}
//MARK: Setting up tableview
func setupTableView() {
tableView.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.white
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
}
class ListMenuCell: UITableViewCell {
//MARK: - Cell initializers
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented.")
}
//MARK: - Cell Properties
let menuLabel: UILabel = {
let label = UILabel()
label.text = ""
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.font = UIFont.boldSystemFont(ofSize: 17)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let menuIcon: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
func setupViews() {
addSubview(menuLabel)
addSubview(menuIcon)
//Note: Don't have to change layoutMarginsGuide to safeAreaLayoutMarginsGuide because its already applied for the table view in which all the below subviews reside in.
menuIcon.layoutMarginsGuide.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true
menuIcon.layoutMarginsGuide.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor, constant: 0).isActive = true
menuIcon.layoutMarginsGuide.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor, constant:0).isActive = true
menuIcon.layoutMarginsGuide.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
menuIcon.layoutMarginsGuide.widthAnchor.constraint(equalTo: menuIcon.layoutMarginsGuide.heightAnchor).isActive = true
menuLabel.layoutMarginsGuide.leadingAnchor.constraint(equalTo: menuIcon.layoutMarginsGuide.trailingAnchor, constant: 20).isActive = true
menuLabel.layoutMarginsGuide.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
menuLabel.layoutMarginsGuide.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor, constant: 0).isActive = true
menuLabel.layoutMarginsGuide.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor, constant:0).isActive = true
}
}
Here is the log for broken constraints:
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600003790a50 UILayoutGuide:0x600002dcb3a0'UIViewLayoutMarginsGuide'.top == UILayoutGuide:0x600002dcb480'UIViewLayoutMarginsGuide'.top (active)>",
"<NSLayoutConstraint:0x600003790aa0 UILayoutGuide:0x600002dcb3a0'UIViewLayoutMarginsGuide'.bottom == UILayoutGuide:0x600002dcb480'UIViewLayoutMarginsGuide'.bottom (active)>",
"<NSLayoutConstraint:0x600003790b40 UILayoutGuide:0x600002dcb3a0'UIViewLayoutMarginsGuide'.height == 40 (active)>",
"<NSLayoutConstraint:0x600003790960 'UIView-bottomMargin-guide-constraint' V:[UILayoutGuide:0x600002dcb480'UIViewLayoutMarginsGuide']-(11)-| (active, names: '|':b2cEngageClient.ListMenuCell:0x7f9250667a40'listMenuCell' )>",
"<NSLayoutConstraint:0x600003791220 'UIView-Encapsulated-Layout-Height' b2cEngageClient.ListMenuCell:0x7f9250667a40'listMenuCell'.height == 62.5 (active)>",
"<NSLayoutConstraint:0x6000037908c0 'UIView-topMargin-guide-constraint' V:|-(11)-[UILayoutGuide:0x600002dcb480'UIViewLayoutMarginsGuide'] (active, names: '|':b2cEngageClient.ListMenuCell:0x7f9250667a40'listMenuCell' )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600003790b40 UILayoutGuide:0x600002dcb3a0'UIViewLayoutMarginsGuide'.height == 40 (active)>
remove this line
menuIcon.layoutMarginsGuide.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor, constant:0).isActive = true
and replace this
menuIcon.bottomAnchor.constraint(lessThanOrEqualTo: self.layoutMarginsGuide.bottomAnchor: -16).isActive = true
make sure you enabled the table automaticDimension
myTable.rowHeight = UITableView.automaticDimension
myTable.estimatedRowHeight = 100
This is the full code for cell
menuIcon.translatesAutoresizingMaskIntoConstraints = false
menuIcon.topAnchor.constraint(equalTo: topAnchor, constant: 16).isActive = true
menuIcon.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
menuIcon.widthAnchor.constraint(equalToConstant: 40).isActive = true
menuIcon.heightAnchor.constraint(equalToConstant: 40).isActive = true
menuIcon.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor,constant: -16).isActive = true
menuLabel.translatesAutoresizingMaskIntoConstraints = false
menuLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
menuLabel.leadingAnchor.constraint(equalTo: menuIcon.trailingAnchor, constant: 16).isActive = true
menuLabel.topAnchor.constraint(equalTo: topAnchor,constant: 16).isActive = true
menuLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -16).isActive = true

Dynamic Constraint for UIView & UICollection

On the top of my screen will show one of two UIViews.
One is the minimized version and the other is the maximized verison.
The minimized view is 30 in height while the maximized version is 250.
Underneath this there is a UICollectionView.
I want it so that when the minimized version of the UIView is showing, the UICollectionView's topAnchor will be connected to the UIViews bottomAnchor.
When I click on each of the UIViews they will become hidden and make the other one visible.
Here are some screenshots to help visualize:
Default
Minimized
Attempt to maximize
So when I show the maximized UIView I want the UICollectionViews topAnchor to be connected to that ones bottomAnchor and so forth.
Currently everything is working except the proper resizing of the UICollectionView.
It will resize up when I minimize, but will not resize down when I maximize.
My viewDidLoad calls both of these functions:
private func configureMaxView() {
view.addSubview(maxView)
maxView.layer.cornerRadius = 18
maxView.backgroundColor = .secondarySystemBackground
maxView.translatesAutoresizingMaskIntoConstraints = false
maxView.isHidden = isMaxViewHidden
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.minimizeAction (_:)))
self.maxView.addGestureRecognizer(gesture)
let padding: CGFloat = 20
NSLayoutConstraint.activate([
maxView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
maxView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
maxView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
maxView.heightAnchor.constraint(equalToConstant: 250),
])
}
private func configureMinView() {
view.addSubview(minView)
minView.layer.cornerRadius = 9
minView.backgroundColor = .secondarySystemBackground
minView.translatesAutoresizingMaskIntoConstraints = false
minView.isHidden = !isMaxViewHidden
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.expandAction (_:)))
self.minView.addGestureRecognizer(gesture)
let padding: CGFloat = 20
NSLayoutConstraint.activate([
minView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
minView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
minView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
minView.heightAnchor.constraint(equalToConstant: 30),
])
}
Here are the functions that are called when you click on one of the UIViews:
#objc func minimizeAction(_ sender:UITapGestureRecognizer){
minView.isHidden = false
maxView.isHidden = true
// without this call the hiding and unhiding works fine - But the collectionview won't move
resizeCollectionView()
isMaxViewHidden = !isMaxViewHidden
}
#objc func expandAction(_ sender:UITapGestureRecognizer){
minView.isHidden = true
maxView.isHidden = false
// without this call the hiding and unhiding works fine - But the collectionview won't move
resizeCollectionView()
isMaxViewHidden = !isMaxViewHidden
}
My viewDidLoad will also call this after setting up the uiviews in order to set up the collectionView:
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UIHelper.createThreeColumnFlowLayout(in: view))
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.backgroundColor = .systemBackground
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: CustomCell.resuseID)
collectionView.translatesAutoresizingMaskIntoConstraints = false
let bottomAnchor = isMaxViewHidden ? minView.bottomAnchor : maxView.bottomAnchor
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
Here is the function I call from each uiview click handler in an attempt to update the constraint:
private func resizeCollectionView() {
let bottomAnchor = isMaxViewHidden ? maxView.bottomAnchor: minView.bottomAnchor
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: bottomAnchor),
])
// This does move the collection view down, but seems hardcoded and bad
//collectionView.frame.origin.y = 650
}
Error:
"<NSLayoutConstraint:0x600000c4ed50 UIView:0x7ffd60c135e0.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c4fa20 UIView:0x7ffd60c135e0.height == 250 (active)>",
"<NSLayoutConstraint:0x600000c4bed0 UIView:0x7ffd60c13750.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c3c000 UIView:0x7ffd60c13750.height == 30 (active)>",
"<NSLayoutConstraint:0x600000c3dbd0 V:[UIView:0x7ffd60c135e0]-(0)-[UICollectionView:0x7ffd62819600] (active)>",
"<NSLayoutConstraint:0x600000c20cd0 V:[UIView:0x7ffd60c13750]-(0)-[UICollectionView:0x7ffd62819600] (active)>"
)
Will attempt to recover by breaking constraint
"<NSLayoutConstraint:0x600000c4ed50 UIView:0x7ffd60c135e0.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c4fa20 UIView:0x7ffd60c135e0.height == 250 (active)>",
"<NSLayoutConstraint:0x600000c4bed0 UIView:0x7ffd60c13750.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c3c000 UIView:0x7ffd60c13750.height == 30 (active)>",
What's happening in here is that, even though the view is being hidden, the constraints are still there, so assigning numerous top constraints on a single view will result to a conflict.
If you want to have a sort of dynamic constraint, just modify the constraint's constant or multiplier values. That way, you wont need to worry about constraint objects.
Wilson's solution is right. What I can just add from that is just have a single containerView containing your min and max views. and just toggle the visibility of your min and max view inside your containerView.
Here's my (simple) take on it:
everything is called in viewDidLoad() as you do, and
you also need to define a constraint object.
private var containerViewHeight: NSLayoutConstraint!
private func configureContainerView() {
containerView = UIView()
containerView.backgroundColor = .systemGreen
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(containerViewTapped)))
view.addSubview(containerView)
containerViewHeight = containerView.heightAnchor.constraint(equalToConstant: 250)
containerViewHeight.isActive = true
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
])
}
#objc private func containerViewTapped() {
isViewTapped.toggle()
containerViewHeight.constant = isViewTapped ? 30 : 250
innerMinView.isHidden = !isViewTapped
innerMaxView.isHidden = isViewTapped
}
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UICollectionViewFlowLayout())
view.addSubview(collectionView)
collectionView.backgroundColor = .systemYellow
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: containerView.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
private func configureInnerMinView() {
innerMinView = UIView()
innerMinView.backgroundColor = .systemRed
innerMinView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(innerMinView)
NSLayoutConstraint.activate([
innerMinView.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.8),
innerMinView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.8),
innerMinView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
innerMinView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
private func configureInnerMaxView() {
innerMaxView = UIView()
innerMaxView.backgroundColor = .systemBlue
innerMaxView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(innerMaxView)
NSLayoutConstraint.activate([
innerMaxView.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.8),
innerMaxView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.8),
innerMaxView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
innerMaxView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
side note: btw. I think I know what project this is. :) SA-GH.F
You're activating conflicting constraints on top of each other. If you want to go between a minimized and maximized view, just change the constant value on the height constraint itself.
I whipped up a sample MyViewController in playgrounds with 2 views, the first of which's height and color changes when you tap it. myView is like your collapsable view, and myOtherView is like your collectionView.
Let me know if you have any questions!
class MyViewController : UIViewController {
private enum ViewState {
case min
case max
var height: CGFloat {
switch self {
case .min:
return 30
case .max:
return 250
}
}
var color: UIColor {
switch self {
case .min:
return .red
case .max:
return .green
}
}
}
private var myViewState: ViewState = .min {
didSet {
viewStateToggled()
}
}
private let myView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let myOtherView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .yellow
return view
}()
private lazy var myViewHeightConstraint = NSLayoutConstraint(
item: myView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: myViewState.height
)
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
configureMyViews()
}
private func configureMyViews() {
view.addSubview(myView)
myView.backgroundColor = myViewState.color
NSLayoutConstraint.activate([
myView.topAnchor.constraint(equalTo: view.topAnchor),
myView.leftAnchor.constraint(equalTo: view.leftAnchor),
myView.rightAnchor.constraint(equalTo: view.rightAnchor),
myViewHeightConstraint
])
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
myView.addGestureRecognizer(tap)
view.addSubview(myOtherView)
NSLayoutConstraint.activate([
myOtherView.topAnchor.constraint(equalTo: myView.bottomAnchor),
myOtherView.leftAnchor.constraint(equalTo: view.leftAnchor),
myOtherView.rightAnchor.constraint(equalTo: view.rightAnchor),
myOtherView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
private func viewStateToggled() {
myViewHeightConstraint.constant = myViewState.height
UIView.animate(withDuration: 0.4) {
self.myView.backgroundColor = self.myViewState.color
self.view.layoutSubviews()
}
}
#objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
myViewState = myViewState == .min ? .max : .min
}
}
I was wondering if you need a call to update your layout. Maybe called from the resizeCollectionView()?
let bottomAnchorMinContraint: NSConstraint = collectionView.topAnchor.constraint(equalTo: minView.bottomAnchor)
let bottomAnchorMaxContraint: NSConstraint = collectionView.topAnchor.constraint(equalTo: maxView.bottomAnchor)
if isMaxViewHidden {
bottomAnchorMinContraint.isActive = true
bottomAnchorMaxContraint.isActive = false
}
if !isMaxViewHidden {
bottomAnchorMinContraint.isActive = false
bottomAnchorMaxContraint.isActive = true
}
view.layoutIfNeeded()

Autolayout warning tableview cell

I am building a chat layout and i have a table view and custom chat cell by adding constraint programmatically but i am getting the autolayout warning
In my ChatMessageCell i have a enum Style to determine the chat layout direction leading or trailing
I have messageLabel which has top, bottom, leading or trailing anchor to the UITableViewCelland width less than equal to 250
I have view which has leading, trailing, bottom and top anchor to the messageLabel view, setMessageLayout method active or deactive the leading or trailing anchor of the view
Autolayout Warning
(
"<NSLayoutConstraint:0x6000039a3c00 UILabel:0x7ff54cc34b20'Hello how are you'.width <= 250 (active)>",
"<NSLayoutConstraint:0x6000039a3e80 H:[UILabel:0x7ff54cc34b20'Hello how are you']-(16)-| (active, names: '|':iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading' )>",
"<NSLayoutConstraint:0x6000039a3e30 iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.leading == UILabel:0x7ff54cc34b20'Hello how are you'.leading - 16 (active)>",
"<NSLayoutConstraint:0x6000039afca0 'UIView-Encapsulated-Layout-Width' iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.width == 375 (active)>"
)
ChatMessageCell
class ChatMessageCell: UITableViewCell {
enum Style {
case leading
case trailing
}
private var style: Style!
private let messageLbl = UILabel()
private let view = UIView()
private var leadingConstraint: NSLayoutConstraint!
private var trailingConstraint: NSLayoutConstraint!
var message: ChatMessage! {
didSet {
messageLbl.text = message.message
setMessageLayout(with: message.sender == "User1" ? .leading : .trailing)
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = #colorLiteral(red: 0.9490196078, green: 0.9450980392, blue: 0.9529411765, alpha: 1)
messageLbl.translatesAutoresizingMaskIntoConstraints = false
messageLbl.font = UIFont(name: "Avenir-Book", size: 17)
messageLbl.numberOfLines = 0
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 5
addSubview(view)
addSubview(messageLbl)
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: messageLbl.topAnchor, constant: -16),
bottomAnchor.constraint(equalTo: messageLbl.bottomAnchor, constant: 16),
messageLbl.widthAnchor.constraint(lessThanOrEqualToConstant: 250)
])
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: messageLbl.topAnchor, constant: -8),
view.bottomAnchor.constraint(equalTo: messageLbl.bottomAnchor, constant: 8),
view.leadingAnchor.constraint(equalTo: messageLbl.leadingAnchor, constant: -8),
view.trailingAnchor.constraint(equalTo: messageLbl.trailingAnchor, constant: 8),
])
leadingConstraint = leadingAnchor.constraint(equalTo: messageLbl.leadingAnchor, constant: -16)
trailingConstraint = trailingAnchor.constraint(equalTo: messageLbl.trailingAnchor, constant: 16)
leadingConstraint.isActive = true
trailingConstraint.isActive = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setMessageLayout(with style: Style) {
messageLbl.textColor = style == .leading ? .black : .white
view.backgroundColor = style == .leading ? .white : #colorLiteral(red: 0.06274509804, green: 0.6039215686, blue: 0.3019607843, alpha: 1)
leadingConstraint.isActive = style == .leading ? true : false
trailingConstraint.isActive = style == .trailing ? true : false
}
}
My Layout
According to your Autolayout warning, messageLabel's width and leading are conflicting.
The warning is telling that both status are active
(
"<NSLayoutConstraint:0x6000039a3c00 UILabel:0x7ff54cc34b20'Hello how are you'.width <= 250 (active)>",
"<NSLayoutConstraint:0x6000039a3e80 H:[UILabel:0x7ff54cc34b20'Hello how are you']-(16)-| (active, names: '|':iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading' )>",
"<NSLayoutConstraint:0x6000039a3e30 iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.leading == UILabel:0x7ff54cc34b20'Hello how are you'.leading - 16 (active)>",
"<NSLayoutConstraint:0x6000039afca0 'UIView-Encapsulated-Layout-Width' iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.width == 375 (active)>"
)
If you read warning carefully, the console may recommend you what to remove.

Centering button with fixed width breaks NSLayoutConstraint

I have an interesting problem. My app is entirely managed through code instead of storyboards. I have a UIViewController which is presented programmatically. It creates a button and constraints for it.
This is what the controller code looks like.
import Foundation
import UIKit
class CreateViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.layer.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .green
return button
}()
func setupViews() {
self.view.addSubview(button)
}
func setupConstraints() {
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button]-100-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[button(50)]-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
}
override func viewDidLoad() {
setupViews()
setupConstraints()
}
}
It throws this error.
(
"<NSLayoutConstraint:0x60000288bb60 UIButton:0x7fa1ab422680.leading == UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'.leading NSSpace(0) (active)>",
"<NSLayoutConstraint:0x60000288bb10 UIButton:0x7fa1ab422680.width == 50 (active)>",
"<NSLayoutConstraint:0x60000288d7c0 UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'.trailing == UIButton:0x7fa1ab422680.trailing NSSpace(0) (active)>",
"<NSLayoutConstraint:0x6000028fe990 'UIView-Encapsulated-Layout-Width' UIView:0x7fa1ab4224a0.width == 375 (active)>",
"<NSLayoutConstraint:0x60000288fe30 'UIView-leftMargin-guide-constraint' H:|-(16)-[UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'](LTR) (active, names: '|':UIView:0x7fa1ab4224a0 )>",
"<NSLayoutConstraint:0x60000288d180 'UIView-rightMargin-guide-constraint' H:[UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide']-(16)-|(LTR) (active, names: '|':UIView:0x7fa1ab4224a0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60000288d7c0 UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'.trailing == UIButton:0x7fa1ab422680.trailing NSSpace(0) (active)>
My aim here is pretty simple. I want the button to be 100 pixels above the bottom, to be centered horizontally, and to have a fixed width and height.
This is how the controller is being instantiated. It exists inside of a
let nav2 = UINavigationController()
let create = CreateViewController()
nav2.viewControllers = [create]
nav2.navigationBar.isTranslucent = false
...
let tabs = UITabBarController()
tabs.viewControllers = [..., nav2, ...]
tabs.delegate = self
Any help in understanding why these constraints are being violated would be greatly appreciated! If any part of the question is unclear, just let me know.
FYI the resulting view looks like this:
Centering an element with Visual Format Language is difficult.
You can easily do it using .centerXAnchor:
class CreateViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
// next line is not needed
// button.layer.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .green
return button
}()
func setupViews() {
self.view.addSubview(button)
}
func setupConstraints() {
NSLayoutConstraint.activate([
// button width of 50
button.widthAnchor.constraint(equalToConstant: 50.0),
// button height of 50
button.heightAnchor.constraint(equalToConstant: 50.0),
// bottom of button 100-pts from bottom of view
// note: contant is negative!
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100),
// center the button horizontally
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
override func viewDidLoad() {
setupViews()
setupConstraints()
}
}
you can try following way to add constraints which is more swifty and cleaner
func setupConstraints() {
let topConstraint = NSLayoutConstraint(item: button,
attribute: .top,
relatedBy: .equal,
toItem: self.view,
attribute: .top,
multiplier: 1,
constant: 100)
let bottomConstraint = NSLayoutConstraint(item: button,
attribute: .left,
relatedBy: .equal,
toItem: self.view,
attribute: .left,
multiplier: 1,
constant: 100)
self.view.addConstraints([topConstraint,bottomConstraint])
}
although I wanted to know why your adding constraints to the button ,when you can declare x and y at the time of initialisation. if you need help in that I've added the code for it too where the button is at center, hope it helps.
lazy var button: UIButton = {
let button = UIButton()
button.frame = CGRect(x: UIScreen.main.bounds.width/2-25, y: UIScreen.main.bounds.height/2-25, width: 50, height: 50)
button.backgroundColor = .green
return button
}()

Resources