skip to Main Content

I’ve coded simple project to show my problem. I created a UITableView, whose top, bottom, left, right pinned to view of ViewController. There is only one cell in UITableView. Inside the cell, there is a UIImageView, whose top, bottom, left and right pinned to those of cell. The cell’s height is AutoDimension. Here my code below:

class ProductSelectionVC: UIViewController {
    
    weak var tableView: UITableView!
    weak var test: UITableViewCell!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Initialize Table View
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        let tableViewLayouts = [
            tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0),
            tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0)
        ]
        self.view.addSubview(tableView)
        NSLayoutConstraint.activate(tableViewLayouts)
    
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        tableView.estimatedRowHeight = 44
        tableView.allowsSelection = false
        tableView.dataSource = self
        tableView.delegate = self
        self.tableView = tableView
            
    }
    
}
extension ProductSelectionVC : UITableViewDelegate , UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = QuantityAddCell()
            cell.product = chanelCoCo
            cell.contentView.layer.borderWidth = 4
            cell.contentView.layer.borderColor = UIColor.red.cgColor
            cell.loadSubViews()
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
}

class QuantityAddCell: UITableViewCell{
    var product: Product!
    weak var testImageView: UIImageView!
    func loadSubViews() {
        let productPicsView = UIImageView()
        productPicsView.translatesAutoresizingMaskIntoConstraints = false
        let productPicsViewLayouts = [
            productPicsView.topAnchor.constraint(equalTo: self.contentView.topAnchor),
            productPicsView.heightAnchor.constraint(equalTo: productPicsView.widthAnchor, multiplier: 3/8, constant: 0),
            productPicsView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor),
            productPicsView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor),
            productPicsView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
        ]
        self.contentView.addSubview(productPicsView)
        self.testImageView = productPicsView
        NSLayoutConstraint.activate(productPicsViewLayouts)
    }
}

If you look at my code, I have set the ratio of UIImageView is 8:3 by set multiplier of 3/8 for the heightAnchor constraint with widthAnchor. Now if I run stimulator, it run completely fine but the debug console inform the me the notices:

2020-11-23 08:13:55.359711+0700 ECommercialTest[1208:24062] [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:0x6000031a5bd0 V:|-(0)-[UIImageView:0x7fdef6a1c3b0]   (active, names: '|':UITableViewCellContentView:0x7fdef6a1be50 )>",
    "<NSLayoutConstraint:0x6000031a5c20 UIImageView:0x7fdef6a1c3b0.height == 0.375*UIImageView:0x7fdef6a1c3b0.width   (active)>",
    "<NSLayoutConstraint:0x6000031a5c70 H:|-(0)-[UIImageView:0x7fdef6a1c3b0](LTR)   (active, names: '|':UITableViewCellContentView:0x7fdef6a1be50 )>",
    "<NSLayoutConstraint:0x6000031a5cc0 UIImageView:0x7fdef6a1c3b0.right == UITableViewCellContentView:0x7fdef6a1be50.right   (active)>",
    "<NSLayoutConstraint:0x6000031a5d10 UIImageView:0x7fdef6a1c3b0.bottom == UITableViewCellContentView:0x7fdef6a1be50.bottom   (active)>",
    "<NSLayoutConstraint:0x6000031a0960 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fdef6a1be50.height == 141   (active)>",
    "<NSLayoutConstraint:0x6000031a13b0 'UIView-Encapsulated-Layout-Width' UITableViewCellContentView:0x7fdef6a1be50.width == 375   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000031a5c20 UIImageView:0x7fdef6a1c3b0.height == 0.375*UIImageView:0x7fdef6a1c3b0.width   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

If you look at the console error. You could see that the width is 375 (the stimulator is iPhone 11 Pro Max) and the height is 140.666667. Meanwhile, with my desired ratio of 8:3, the height should be 140.625 (375 * 3 / 8). I believe it is the reason why the constraints conflict.

But the funny thing is if I replace 3/8 with 2, 3, 5 or any not-decimal numbers; or any fraction that 375 could divide and produce no remainder, for example 5, the height would be 75. The console are happy with no comments, no error. So constraints only struggle with multiplier produce remainder. Again, the stimulator is running fine.

My Xcode is 12

Would anyone helping with this problem please?

3

Answers


  1. Firstly, you use xib for UITableViewCell, then you can use auto layout in xib.
    Secondly, in your cell:

    let productPicsViewLayouts = [
                productPicsView.topAnchor.constraint(equalTo: self.contentView.topAnchor),
                productPicsView.heightAnchor.constraint(equalTo: productPicsView.widthAnchor, multiplier: 3/8, constant: 0),
                productPicsView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor),
                productPicsView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor),
                productPicsView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
        ]
    

    This make conflict because UIImageView had constraint height, width equal contentView of cell. So, productPicsView.heightAnchor.constraint(equalTo: productPicsView.widthAnchor, multiplier: 3/8, constant: 0) not need. Or if you want keep multiplier of UIImageView you need remove productPicsView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor)

    Login or Signup to reply.
  2. I think you should change the priority of Image’s height constraint
    so try to set it 750 or 250 that might resolve the issue

    Login or Signup to reply.
  3. UIKit will not try to draw on "partial pixels"

    As you noted, your specified ratio of 8:3 on a device with 375 point width results in a height of 140.625 (375 * 3 / 8).

    So, you need to tell auto-layout which constraint you want to allow it to modify. In most cases such as yours, that would be the image view’s height.

        // UIKit will not try to draw on "partial pixels" so
        //  use .defaultHigh for height constraint to avoid auto-layout issues
        let heightConstraint = productPicsView.heightAnchor.constraint(equalTo: productPicsView.widthAnchor, multiplier: 3.0/8.0, constant: 0)
        heightConstraint.priority = .defaultHigh
        
        let productPicsViewLayouts = [
            productPicsView.topAnchor.constraint(equalTo: self.contentView.topAnchor),
            heightConstraint,
            productPicsView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor),
            productPicsView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor),
            productPicsView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
        ]
    

    This will get rid of the auto-layout complaints.

    As a side note, you’re doing a number of things wrong.

    1 – You want to register your custom cell class for reuse. In your code, you’re registering a default UITableViewCell — but then never actually using it.

    2 – Following that comment, your cellForRowAt is not dequeuing a cell for reuse.

    3 – You cellForRowAt is calling cell.loadSubViews() … but all of that code should be in your cell’s initialization.

    Here’s an updated version of your code:

    class ProductSelectionVC: UIViewController {
        
        weak var tableView: UITableView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //Initialize Table View
            let tableView = UITableView()
            tableView.translatesAutoresizingMaskIntoConstraints = false
            let tableViewLayouts = [
                tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
                tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
                tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0),
                tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0)
            ]
            self.view.addSubview(tableView)
            NSLayoutConstraint.activate(tableViewLayouts)
            
            // register your custom cell for reuse
            tableView.register(QuantityAddCell.self, forCellReuseIdentifier: "Cell")
            
            tableView.estimatedRowHeight = 44
            tableView.allowsSelection = false
            tableView.dataSource = self
            tableView.delegate = self
            self.tableView = tableView
        }
        
    }
    
    extension ProductSelectionVC : UITableViewDelegate , UITableViewDataSource{
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 1
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! QuantityAddCell
            
            // set the cell data here
            //  for example:
            if let img = UIImage(systemName: "person.circle") {
                cell.productPicsView.image = img
            }
            
            return cell
        }
        
        // this is not needed
        //func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        //  return UITableView.automaticDimension
        //}
    }
    
    class QuantityAddCell: UITableViewCell{
        
        let productPicsView = UIImageView()
        
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() -> Void {
            
            // set up your cell's subviews and other properties here
            
            productPicsView.translatesAutoresizingMaskIntoConstraints = false
            
            // UIKit will not try to draw on "partial pixels" so
            //  use .defaultHigh for height constraint to avoid auto-layout issues
            let heightConstraint = productPicsView.heightAnchor.constraint(equalTo: productPicsView.widthAnchor, multiplier: 3.0/8.0, constant: 0)
            heightConstraint.priority = .defaultHigh
            
            let productPicsViewLayouts = [
                productPicsView.topAnchor.constraint(equalTo: self.contentView.topAnchor),
                heightConstraint,
                productPicsView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor),
                productPicsView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor),
                productPicsView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
            ]
            self.contentView.addSubview(productPicsView)
            NSLayoutConstraint.activate(productPicsViewLayouts)
            contentView.layer.borderWidth = 4
            contentView.layer.borderColor = UIColor.red.cgColor
        }
    }
    

    Edit

    A little clarification…

    When using Automatic Dimension for table view row heights (the default), auto-layout doesn’t like partial pixels.

    You certainly can constrain a UIImageView (by itself) to a height of 375 * 3 / 8 (140.625) and you won’t get auto-layout complaints.

    Also, if you explicitly set the row height:

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 375.0 * 3.0 / 8.0
    }
    

    You won’t get auto-layout complaints.

    However, we very often want the row height to be set by the cell’s content, which is the whole point.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search