以编程方式添加 NSLayout 约束的问题
Problem with adding NSLayout constraints programmatically
我在当前项目中使用 greaterThanOrEqualTo 约束时遇到问题。
我需要的是使单元格的高度动态化,因此我需要根据 API 中的 returns 将食谱的标题设为 multi-lines,并制作最喜欢的按钮从 trailingAnchor 中获取常量约束。但是我得到的是截图。
如果我使用 XIB,我会很容易做到,但这是我第一次以编程方式制作 UI。
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()
}
}
主页视图:
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
}
}
}
您可以将尾部的 foodTitleLabel 更改为 equalTo 将解决此问题。带有标签和最喜欢的按钮。但是如果你仍然不希望它是 equalTo 那么在这种情况下你已经在使用负值你知道你没有扭转你必须使用的约束 lessThanOrEqualTo 来实现你想做的事情。这与您在情节提要中所做的完全相反。
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)
])
}
此外,在您设置 tablview 的主视图中使用这些您已经拥有处理自动 dymintions 的代码,但您已经注释掉了估计高度,也没有评论
foodTableView.estimatedRowHeight = 100
foodTableView.rowHeight = UITableView.automaticDimension
更新:
您必须更改 foodTitleLabel
上的内容拥抱和内容优先级
foodTitle.setContentHuggingPriority(.init(240.0), for: .horizontal)
foodTitle.setContentCompressionResistancePriority(.init(740.0), for: .horizontal)
内容拥抱优先级:
设置视图拒绝大于其固有大小的优先级。
内容压缩优先级:
设置视图拒绝小于其固有大小的优先级。
这就是为什么目前的布局试图让您最喜欢的按钮小于标题标签
我在当前项目中使用 greaterThanOrEqualTo 约束时遇到问题。
我需要的是使单元格的高度动态化,因此我需要根据 API 中的 returns 将食谱的标题设为 multi-lines,并制作最喜欢的按钮从 trailingAnchor 中获取常量约束。但是我得到的是截图。
如果我使用 XIB,我会很容易做到,但这是我第一次以编程方式制作 UI。
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()
}
}
主页视图:
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
}
}
}
您可以将尾部的 foodTitleLabel 更改为 equalTo 将解决此问题。带有标签和最喜欢的按钮。但是如果你仍然不希望它是 equalTo 那么在这种情况下你已经在使用负值你知道你没有扭转你必须使用的约束 lessThanOrEqualTo 来实现你想做的事情。这与您在情节提要中所做的完全相反。
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)
])
}
此外,在您设置 tablview 的主视图中使用这些您已经拥有处理自动 dymintions 的代码,但您已经注释掉了估计高度,也没有评论
foodTableView.estimatedRowHeight = 100
foodTableView.rowHeight = UITableView.automaticDimension
更新: 您必须更改 foodTitleLabel
上的内容拥抱和内容优先级foodTitle.setContentHuggingPriority(.init(240.0), for: .horizontal)
foodTitle.setContentCompressionResistancePriority(.init(740.0), for: .horizontal)
内容拥抱优先级: 设置视图拒绝大于其固有大小的优先级。
内容压缩优先级: 设置视图拒绝小于其固有大小的优先级。
这就是为什么目前的布局试图让您最喜欢的按钮小于标题标签