I am struggling to set the necessary constraints to limit the width of a label to the leading anchor of a button in a tableview cell.
Desired Result
Label to the left, button to the right. Label wraps before button if needed.
Current Result
The label is pulling the red button to the left.
Code
[button.centerYAnchor constraintEqualToAnchor:cell.contentView.centerYAnchor constant:0].active = YES;
[button.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-5].active = YES;
[label.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:10].active = YES;
[label.trailingAnchor constraintEqualToAnchor:button.leadingAnchor constant:0].active = YES;
I have tried various things like adding another label constraint to the right of the cell but that squeezed the button (I tried to fix by setting its compression resistance priority but that had no effect)
What constraints do I need to achieve the desired result please?
Just change
[label.trailingAnchor constraintEqualToAnchor:button.leadingAnchor constant:0].active = YES;
into
[label.trailingAnchor constraintLessThanOrEqualToAnchor:button.leadingAnchor constant:0].active = YES;
import UIKit
final class ViewController: UIViewController {
lazy var tableView: UITableView = {
let val = UITableView(frame: self.view.bounds, style: .plain)
val.dataSource = self
val.delegate = self
val.estimatedRowHeight = 44
val.register(MyCell.self, forCellReuseIdentifier: "MyCell")
return val
}()
let data: [String] = [
"LayoutDemo",
"LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo LayoutDemo",
"LayoutDemoLayoutDemoLayoutDemoLayoutDemo"
]
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as? MyCell else { fatalError() }
cell.dataText = data[indexPath.row]
return cell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
class MyCell: UITableViewCell {
var dataText: String? {
didSet {
label.text = dataText
}
}
lazy var label: UILabel = {
let val = UILabel(frame: .zero)
val.translatesAutoresizingMaskIntoConstraints = false
val.numberOfLines = 0
val.backgroundColor = .gray
return val
}()
lazy var button: UIButton = {
let val = UIButton(type: .custom)
val.translatesAutoresizingMaskIntoConstraints = false
val.backgroundColor = .red
return val
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(label)
contentView.addSubview(button)
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
button.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5),
button.heightAnchor.constraint(equalToConstant: 20),
button.widthAnchor.constraint(equalToConstant: 20),
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
label.trailingAnchor.constraint(lessThanOrEqualTo: button.leadingAnchor, constant: -10)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Related
I am trying to create a UITableView that has a hidden subview at the bottom that will slide open when the cell is tapped. I have the following demo code:
class ViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
tableView.separatorStyle = .none
tableView.register(ExpandTableCell.self, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
return cell ?? UITableViewCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandTableCell else { return }
tableView.performBatchUpdates({ cell.animate() }, completion: nil)
}
}
And the cell:
class ExpandTableCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
setupViews()
}
private let blueView = UIView()
// MARK: - Views
private func setupViews() {
selectionStyle = . none
let titleLabel = UILabel()
titleLabel.text = "Some Title"
let subtitleLabel = UILabel()
subtitleLabel.text = "Some othere sdfhdslkjl dsfljdslfj sdlj sdfldsjfldsjf sdfjdslfjds"
subtitleLabel.numberOfLines = 2
blueView.backgroundColor = .blue
blueView.translatesAutoresizingMaskIntoConstraints = false
blueView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel, blueView])
stackView.axis = .vertical
stackView.spacing = 8.0
blueView.isHidden = true
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
func animate() {
UIView.animate(withDuration: 0.1, animations: { [blueView] in
blueView.isHidden.toggle()
})
}
}
The problem is that the animation has the following effect:
It's squashing the contents of the label above it. It should just slide down from the bottom.
What am I doing wrong here?
Just change the animation timing to match that of the tableView. Try 0.3
func animate() {
UIView.animate(withDuration: 0.3, animations: { [blueView] in
blueView.isHidden.toggle()
})
}
The artifact is gone.
I am creating a tableview programmatically which is not a problem,
but making a programmatic table view cell is being a headache, it is the first time I do this.
I don't know what I am doing wrong as much as I have tried, I cannot debug this.
Here is the code for the view controller
import Foundation
import UIKit
extension CountryPhoneCodeList {
class ViewController : UIViewController {
var viewModel: ViewModel!
var router: Router!
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.translatesAutoresizingMaskIntoConstraints = false
table.dataSource = self
table.delegate = self
table.register(CountryPhoneCodeListCell.self, forCellReuseIdentifier: "CountryPhoneCodeListCell")
table.layer.backgroundColor = UIColor.white.cgColor
table.estimatedRowHeight = 44
table.rowHeight = UITableView.automaticDimension
table.separatorStyle = .none
return table
}()
var updateTextFieldCode : ((String, String) -> ())? = nil
override func viewDidLoad() {
self.configureUI()
}
private func configureUI(){
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
}
extension CountryPhoneCodeList.ViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedViewModel = viewModel.cellViewModelAt(section: indexPath.section, row: indexPath.row)
updateTextFieldCode?(selectedViewModel.flag, selectedViewModel.countryPhoneCode)
router.dismiss()
}
}
extension CountryPhoneCodeList.ViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.countryCodeListCellViewModels.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let character = viewModel.getAlphabetCharacterFromIndex(index: section)
guard let countryCodesStartingWithCharacter = viewModel.countryCodeListCellViewModels[character] else {fatalError("Could not get codes starting with letter \(character)")}
return countryCodesStartingWithCharacter.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect.zero)
let label = UILabel(frame: CGRect(x: 14, y: -8, width: 50, height: 50))
label.text = viewModel.getAlphabetCharacterFromIndex(index: section)
label.customise(for: .bodyBold)
view.addSubview(label)
view.backgroundColor = .white
return view
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "CountryPhoneCodeListCell", for: indexPath) as? CountryPhoneCodeListCell {
cell.viewModel = viewModel.cellViewModelAt(section: indexPath.section, row: indexPath.row)
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
}
class CountryPhoneCodeListViewController : CountryPhoneCodeList.ViewController {}
And this is the code for the tableview cell
import UIKit
struct CountryPhoneCodeListCellViewModel {
var countryName = ""
var countryPhoneCode = ""
var flag = ""
}
class CountryPhoneCodeListCell: UITableViewCell {
private var countryNameLabel: UILabel = .buildBodyLabel()
private var countryPhoneCodeLabel: UILabel = .buildBodyLabel()
var viewModel: CountryPhoneCodeListCellViewModel? {
didSet {
guard let viewModel = viewModel else {fatalError("Cannot unwrap viewModel")}
countryNameLabel.text = viewModel.countryName
countryNameLabel.customise(for: .body)
countryPhoneCodeLabel.text = viewModel.countryPhoneCode
countryPhoneCodeLabel.customise(for: .body)
}
}
override func prepareForReuse() {
countryNameLabel.text = nil
countryPhoneCodeLabel.text = nil
}
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(countryNameLabel)
contentView.addSubview(countryPhoneCodeLabel)
NSLayoutConstraint.activate([
countryNameLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: .constant16),
countryNameLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: .constant16),
countryNameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .constant4),
countryPhoneCodeLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .constant16),
countryPhoneCodeLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: .constant16),
countryPhoneCodeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .constant16)
])
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
in my project I want that after presenting viewController, tableView popup from bottom of screen until showing all the content of tableView. the UITableViewCell height is dynamic and I'm using swift language and NSLayoutConstraint in my project. how can I do this? it's only showing some part of tableView.
this is my codes:
class myViewController: UIViewController {
var tableView: UITableView!
var tableViewTopLayoutConstraint, tableViewHeightLayoutConstraint: NSLayoutConstraint!
private var isFirstTime: Bool = true
override func viewDidLayoutSubviews() {
print("this is tableView frame Height:\(tableView.frame.height)")
print("this is tableView content Height:\(tableView.contentSize.height)")
if tableView.contentSize.height != 0 && isFirstTime {
tableViewHeightLayoutConstraint.constant = tableView.contentSize.height
showContainerView(-tableView.contentSize.height)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
tableView = UITableView()
tableView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
tableView.layer.cornerRadius = 20
tableView.register(SomeUITableViewCell.self, forCellReuseIdentifier: SomeUITableViewCell.identifier)
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.tableFooterView = nil
tableView.isScrollEnabled = false
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableViewTopLayoutConstraint = tableView.topAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
tableViewTopLayoutConstraint.isActive = true
tableViewHeightLayoutConstraint = tableView.heightAnchor.constraint(equalToConstant: 0)
tableViewHeightLayoutConstraint.isActive = true
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
private func showContainerView(_ viewHeight: CGFloat) {
isFirstTime = false
tableViewTopLayoutConstraint.constant = viewHeight
UIView.animate(withDuration: 0.5) { [weak self] in
self?.view.layoutIfNeeded()
}
}
}
extension myViewController: 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: SomeUITableViewCell.identifier, for: indexPath) as! SomeUITableViewCell
cell.setCell()
return cell
}
}
class SomeUITableViewCell: UITableViewCell {
static let identifier = "SomeUITableViewCellId"
private var cardOrDepositNumberLabel: UILabel!
private var cardOrDepositOwnerLabel: UILabel!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
cardOrDepositNumberLabel = UILabel()
cardOrDepositNumberLabel.numberOfLines = 0
cardOrDepositNumberLabel.backgroundColor = .red
cardOrDepositNumberLabel.textAlignment = .right
cardOrDepositNumberLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(cardOrDepositNumberLabel)
NSLayoutConstraint.activate([
cardOrDepositNumberLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
cardOrDepositNumberLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
cardOrDepositNumberLabel.topAnchor.constraint(equalTo: topAnchor),
])
cardOrDepositOwnerLabel = UILabel()
cardOrDepositOwnerLabel.numberOfLines = 0
cardOrDepositOwnerLabel.textAlignment = .right
cardOrDepositOwnerLabel.backgroundColor = .blue
cardOrDepositOwnerLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(cardOrDepositOwnerLabel)
NSLayoutConstraint.activate([
cardOrDepositOwnerLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
cardOrDepositOwnerLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
cardOrDepositOwnerLabel.topAnchor.constraint(equalTo: cardOrDepositNumberLabel.bottomAnchor),
cardOrDepositOwnerLabel.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setCell() {
cardOrDepositNumberLabel.text = "some data some data some data"
cardOrDepositOwnerLabel.text = "some data some data some data some data some data some data some data some data some data some data"
}
}
Problem is here
tableViewHeightLayoutConstraint = tableView.heightAnchor.constraint(equalToConstant: 300)
set an intial no zero value and do what is inside viewDidLayoutSubviews in viewDidAppear preferably after a delay to give some time to the table to calculate it's actualy contentSize
I am trying to build a TableView programmatically, but I cannot get a basic standard label to display; all I see is basic empty cells. Here's my code:
TableView Cell:
class TableCell: UITableViewCell {
let cellView: UIView = {
let view = UIView()
view.backgroundColor = .systemRed
return view
}()
let labelView: UILabel = {
let label = UILabel()
label.text = "Cell 1"
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
addSubview(cellView)
NSLayoutConstraint.activate([
cellView.topAnchor.constraint(equalTo: topAnchor),
cellView.bottomAnchor.constraint(equalTo: bottomAnchor),
cellView.leadingAnchor.constraint(equalTo: leadingAnchor),
cellView.trailingAnchor.constraint(equalTo: trailingAnchor)])
cellView.addSubview(labelView)
}
}
Data Source:
class TableDataSource: NSObject, UITableViewDataSource {
let cellID = "cell"
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! TableCell
return cell
}
}
And this is the VC:
class TableViewController: UITableViewController {
let dataSource = TableDataSource()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TableCell.self, forCellReuseIdentifier: dataSource.cellID)
tableView.dataSource = dataSource
}
}
I am trying to keep the code as basic as possible for future references. I've set various breakpoints to see what could go wrong, but they all check out. Could it be the constraints that are wrong?
Any help is appreciated.
I see several errors in your cell.
Add subviews to contentView, not directly to cell:
contentView.addSubview(cellView)
cellView.addSubview(labelView)
The same is necessary for constraints:
NSLayoutConstraint.activate([
cellView.topAnchor.constraint(equalTo: contentView.topAnchor),
cellView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
cellView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
cellView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
Views created in code need to set translatesAutoresizingMaskIntoConstraints = false,
let cellView: UIView = {
let view = UIView()
view.backgroundColor = .systemRed
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let labelView: UILabel = {
let label = UILabel()
label.text = "Cell 1"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
There are no constraints for your label.
Your constraints don't work, because you need to change translatesAutoresizingMaskIntoConstraints for cellView in your setup():
func setup() {
addSubview(cellView)
cellView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cellView.topAnchor.constraint(equalTo: topAnchor),
cellView.bottomAnchor.constraint(equalTo: bottomAnchor),
cellView.leadingAnchor.constraint(equalTo: leadingAnchor),
cellView.trailingAnchor.constraint(equalTo: trailingAnchor)])
cellView.addSubview(labelView)
}
I'm having problems displaying this UITableView under the UIImageView. It does this weird thing, where you can't see the table or anything at all, however when you scroll up or down, you can see the blue cell appear in the 40px high UITableView.
class TvDetailHeader: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {
private let contributorCellId = "contributorCellId"
let thumbnailImageView: UIImageView = {
let imageView = UIImageView(image: #imageLiteral(resourceName: "stranger things poster"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.cornerRadius = 6
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
let contributorTableView: UITableView = {
let tableView = UITableView()
tableView.separatorStyle = .none
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .blue
return tableView
}()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: contributorCellId, for: indexPath) as! ContributorCell
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
override func setupViews() {
super.setupViews()
contributorTableView.delegate = self
contributorTableView.dataSource = self
contributorTableView.register(ContributorCell.self, forCellReuseIdentifier: contributorCellId)
backgroundColor = .white
addSubview(thumbnailImageView)
addSubview(contributorTableView)
thumbnailImageView.topAnchor.constraint(equalTo: topAnchor, constant: 25).isActive = true
thumbnailImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 20).isActive = true
thumbnailImageView.widthAnchor.constraint(equalToConstant: 60).isActive = true
thumbnailImageView.heightAnchor.constraint(equalToConstant: 90).isActive = true
contributorTableView.topAnchor.constraint(equalTo: thumbnailImageView.bottomAnchor, constant: 10).isActive = true
contributorTableView.leftAnchor.constraint(equalTo: leftAnchor, constant: 20).isActive = true
contributorTableView.widthAnchor.constraint(equalToConstant: frame.width).isActive = true
contributorTableView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
}
class ContributorCell: UITableViewCell {
let cellView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(cellView)
backgroundColor = .blue
cellView.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
cellView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
cellView.widthAnchor.constraint(equalToConstant: 30).isActive = true
cellView.heightAnchor.constraint(equalToConstant: frame.height).isActive = true
}
}