0

I'm having an issue with using greaterThanOrEqualTo constraint with my current project.

enter image description here

What I need is making the height of the cell dynamic, so I need the title of recipes to be multi-lines based on what returns from the API, and make the favorite button take constant constraint from trailingAnchor. But what I got is in the screenshot.

If I used XIB I would have done it easily but it's my first time making UI programmatically.

HomeTableViewCell:

class HomeTableViewCell: UITableViewCell {

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        layoutUI()
        selectionStyle = .none
        self.backgroundColor = .clear
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    lazy var containerView: UIView = {
        let containerView = UIView()
        containerView.backgroundColor = .white
        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.layer.cornerRadius = 8.0
//        containerView.clipsToBounds = true
        return containerView
    }()

    lazy var foodImage: UIImageView = {
        let foodImage = UIImageView()
        foodImage.translatesAutoresizingMaskIntoConstraints = false
        foodImage.contentMode = .scaleAspectFill
        foodImage.clipsToBounds = true
        foodImage.layer.cornerRadius = 8.0
        return foodImage
    }()

    lazy var favouriteButton: UIButton = {
        var favouriteButton = UIButton()
        favouriteButton.setImage(UIImage(systemName: "heart"), for: .normal)
        favouriteButton.tintColor = .red
        favouriteButton.translatesAutoresizingMaskIntoConstraints = false
        return favouriteButton
    }()

    lazy var foodTitle: UILabel = {
        let foodTitle = UILabel()
        foodTitle.textColor = .CustomGreen()
        foodTitle.numberOfLines = 0
        foodTitle.translatesAutoresizingMaskIntoConstraints = false
        return foodTitle
    }()

    func setupContainerView() {
        NSLayoutConstraint.activate([
            containerView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
            containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16),
            containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
        ])
    }

    func setupFoodImage() {
        NSLayoutConstraint.activate([
            foodImage.topAnchor.constraint(equalTo: containerView.topAnchor),
            foodImage.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            foodImage.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            foodImage.heightAnchor.constraint(equalToConstant: self.bounds.width / 1.8)
        ])
    }

    func setupFoodTitle() {
        NSLayoutConstraint.activate([
            foodTitle.topAnchor.constraint(equalTo: foodImage.bottomAnchor, constant: 16),
            foodTitle.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16),
            foodTitle.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
            foodTitle.trailingAnchor.constraint(greaterThanOrEqualTo: favouriteButton.leadingAnchor, constant: -16)

        ])
    }

    func setupFavouriteButtonConstraints() {
        NSLayoutConstraint.activate([
            favouriteButton.centerYAnchor.constraint(equalTo: foodTitle.centerYAnchor),
            favouriteButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16)
        ])
    }

    func addSubview() {
        addSubview(containerView)
        containerView.addSubview(foodImage)
        containerView.addSubview(foodTitle)
        containerView.addSubview(favouriteButton)
    }

    func layoutUI() {
        addSubview()
        setupContainerView()
        setupFoodImage()
        setupFoodTitle()
        setupFavouriteButtonConstraints()
    }

}

HomeView:

class HomeView: UIView {

    var recipes: Recipes?
    var recipesDetails = [Recipe]()
    let indicator = ActivityIndicator()

    let categories = ["italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food"]

    override init( frame: CGRect) {
        super.init(frame: frame)
        layoutUI()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    lazy var foodTableView: UITableView = {
        let foodTableView = UITableView()
        foodTableView.translatesAutoresizingMaskIntoConstraints = false
        foodTableView.backgroundColor = #colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1)
        foodTableView.delegate = self
        foodTableView.dataSource = self
        foodTableView.register(CategoriesTableViewCellCollectionViewCell.self, forCellReuseIdentifier: "CategoriesTableViewCellCollectionViewCell")
        foodTableView.register(HomeTableViewCell.self, forCellReuseIdentifier: "HomeTableViewCell")
        foodTableView.rowHeight = UITableView.automaticDimension
//        foodTableView.estimatedRowHeight = 100
        foodTableView.showsVerticalScrollIndicator = false
        foodTableView.separatorStyle = .none
        return foodTableView
    }()

    func setupFoodTableView() {
        NSLayoutConstraint.activate([
            foodTableView.topAnchor.constraint(equalTo: topAnchor),
            foodTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
            foodTableView.leadingAnchor.constraint(equalTo: leadingAnchor),
            foodTableView.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])
    }

    func addSubview() {
        addSubview(foodTableView)
    }

    func layoutUI() {
        indicator.setupIndicatorView(self, containerColor: .customDarkGray(), indicatorColor: .white)
        addSubview()
        setupFoodTableView()
        fetchData()

    }

    func fetchData() {
        AF.request("apilink.com").responseJSON { (response) in
            if let error = response.error {
                print(error)
            }
            do {
                if let data = response.data {
                    self.recipes = try JSONDecoder().decode(Recipes.self, from: data)
                    self.recipesDetails = self.recipes?.recipes ?? []
                    DispatchQueue.main.async {
                        self.foodTableView.reloadData()
                    }
                }

            } catch {
                print(error)
            }
            self.indicator.hideIndicatorView()
        }
    }

}

extension HomeView: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return recipesDetails.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if indexPath.row == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CategoriesTableViewCellCollectionViewCell", for: indexPath) as! CategoriesTableViewCellCollectionViewCell
//            cell.layoutIfNeeded()
            cell.collectionView.reloadData()
            return cell
        }

        let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
        let url = URL(string: recipesDetails[indexPath.row].image ?? "Error")
        cell.foodImage.kf.setImage(with: url)
        cell.foodTitle.text = recipesDetails[indexPath.row].title
//        cell.layoutIfNeeded()
        return cell

    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.row == 0 {
            return 160
        } else {
            return UITableView.automaticDimension
        }

    }

}
6
  • Why do you use greaterThanOrEqualTo for the trailing constraint of the label? Simply changing it to equalTo would probably fix your problem. Also, using negative values for constants in constraints can lead to confusion, always try to reverse the constraint if needed, so the constants are positive. Commented Mar 4, 2020 at 12:07
  • @pckill I'm using it for making the containerView dynamic based on recipes name Commented Mar 4, 2020 at 12:10
  • Yeah, but that is a trailing constraint, it can be fixed. The height of the label should define dynamic cell height, not its width. Commented Mar 4, 2020 at 12:12
  • @pckill And how to make the height of the label dynamic, please? I tried to implement a dynamic height based on greaterThanOrEqualTo but still the same problem Commented Mar 4, 2020 at 12:27
  • Have you tried using UIStackView? Commented Mar 4, 2020 at 13:24

1 Answer 1

0

You can change the foodTitleLabel trailing to equalTo will fix the issue. with the label and the favorite button. but if still you don't want that to be equalTo then in this case as you are already using the negative values you know that you have not reversed your constraints you have to use lessThanOrEqualTo to achieve what you want to do. it's the exact reverse of what you might do in a storyboard.

func setupFoodTitle() {
    NSLayoutConstraint.activate([
        foodTitle.topAnchor.constraint(equalTo: foodImage.bottomAnchor, constant: 16),
        foodTitle.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16),
        foodTitle.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
        foodTitle.trailingAnchor.constraint(lessThanOrEqualTo: favouriteButton.leadingAnchor, constant: -16)

    ])
}

Also in the Home view where you are setting your tablview use use these you already have the code that takes care of automatic dymintions but you have commented out the estimated height un comment that as well

foodTableView.estimatedRowHeight = 100
foodTableView.rowHeight = UITableView.automaticDimension

Update: You have to change the content hugging and content comprision priority on the foodTitleLabel

foodTitle.setContentHuggingPriority(.init(240.0), for: .horizontal)
foodTitle.setContentCompressionResistancePriority(.init(740.0), for: .horizontal)

Content hugging priority: Sets the priority with which a view resists being made larger than its intrinsic size.

Content Compression priority: Sets the priority with which a view resists being made smaller than its intrinsic size.

That's why the layout at the moment trys to make you're favorite button smaller than the title Label

Sign up to request clarification or add additional context in comments.

3 Comments

Still the same problem, I test it on iPhone XR and iPhone SE. iPhone SE: ibb.co/3r6zfF6 iPhone XR: ibb.co/tDCX8FF
@AhmedAbdElaziz change the content priorities it will start working as I can see in the new screen shots that you uploaded now it has the issue with content priorities.
Thank you so much for your help. It works perfectly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.