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
        }

    }

}
  • 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. – pckill Mar 04 '20 at 12:07
  • @pckill I'm using it for making the containerView dynamic based on recipes name – Ahmed Abd Elaziz Mar 04 '20 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. – pckill Mar 04 '20 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 – Ahmed Abd Elaziz Mar 04 '20 at 12:27
  • Have you tried using UIStackView? – xpereta Mar 04 '20 at 13:24
  • @AhmedAbdElaziz - I posted this link - [gist](https://gist.github.com/DonMag/e70610ab127adcc426f90a91a26d4149) - in reply to your comment at this question: https://stackoverflow.com/a/60509177/6257435 ... I updated that code with your added `favouriteButton` – DonMag Mar 04 '20 at 15:49

1 Answers1

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

Khalid Afridi
  • 913
  • 5
  • 12