custom tableviewcell dynamic height not updating as per constraints (with programmatically autolayout) - ios

I have a custom uitableviewcell in app, I am trying to update its length dynamically based on its subviews (labels) contents.
but it's not working.
find related code as below.
class TransactionTableViewCell: UITableViewCell{
private lazy var dateLabel: UILabel = {
let datelabel = UILabel()
datelabel.textColor = .label
datelabel.backgroundColor = .red
datelabel.lineBreakMode = .byTruncatingTail
datelabel.numberOfLines = 0
datelabel.translatesAutoresizingMaskIntoConstraints = false
return datelabel
}()
another method in same class:
func setupDefaultUI(){
self.contentView.addSubview(dateLabel)
}
override func awakeFromNib() {
super.awakeFromNib()
setupDefaultUI()
buildConstraints()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupDefaultUI()
buildConstraints()
}
func buildConstraints(){
let marginGuide = self.contentView
NSLayoutConstraint.activate([
dateLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor)
,
dateLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor),
dateLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor),
dateLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 1.0)
,
dateLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor)
])
}
and inside viewcontroller file:
private lazy var transactionTableView: UITableView = {
let tableview = UITableView.init()
tableview.backgroundView = nil
tableview.backgroundColor = .clear
tableview.rowHeight = UITableView.automaticDimension
tableview.estimatedRowHeight = 300
return tableview
}()
and in viewdidload:
transactionTableView.dataSource = Objdatasource
transactionTableView.delegate = Objdelegate
transactionTableView.register(TransactionTableViewCell.self, forCellReuseIdentifier: CellIdentifiers.transactionCell)

Remove this
dateLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 1.0)

In this tutorial (which I recently followed and worked for me), there is a function you are missing there:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}

Related

Swift Table View Cell Value Failed to display vertically

I am new to swift . I am creating table view cell programatically with the sub title . I defined the two label and two function to get the value form API and display it into label control but the problem is the values of label are displayed into correct position . I want to display the title and then below the title is sub title values . Here is the screen shot .
Here is the code table view cell.
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.rovers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: StoryCell.identifier , for: indexPath) as? StoryCell
else{ return UITableViewCell()}
let row = indexPath.row
let title = viewModel.getTitle(by: row)
cell.configureCell(title:title)
let Id = viewModel.getId(by: row)
cell.configureCell(Id: Id)
return cell
}
}
Here is code for configure the label .
import UIKit
class StoryCell: UITableViewCell {
static let identifier = "StoryCell"
private lazy var storyTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
private lazy var storyIdLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpUI()
setUpUIID()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(title: String) {
storyTitleLabel.text = "Status :\(title)"
}
func configureCell(Id: Int) {
storyIdLabel.text = "Id: \(String(Id))"
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
private func setUpUI() {
contentView.addSubview(storyTitleLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyTitleLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyTitleLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
storyTitleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyTitleLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}
private func setUpUIID() {
contentView.addSubview(storyIdLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyIdLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyIdLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
storyIdLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyIdLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}
}
I made some changes in your code check below. Now it should be fine.
private func setUpUI() {
contentView.addSubview(storyTitleLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyTitleLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyTitleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyTitleLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}
private func setUpUIID() {
contentView.addSubview(storyIdLabel)
let safeArea = contentView.safeAreaLayoutGuide
storyIdLabel.topAnchor.constraint(equalTo: storyTitleLabel.topAnchor).constant = 5
storyIdLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
storyIdLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyIdLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}

How to add subview to stackview in table view cell?

I am in need to add the label as subview to UIStackView in table view cell.
I have created label as
let nameLabel=UILabel()
nameLabel.text=names[indexPath.row]
Where name is an array which is a type of String
My code is
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource
{
let names=["Amutha","Priya","Amuthapriya","Priyasri","Kavisha"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell
{
let cell=tableView.dequeueReusableCell(withIdentifier: "cell") as! ViewCell
for name in names
{
let nameLabel=UILabel()
nameLabel.text=name
cell.nameStackView!.addSubview(nameLabel)
}
return cell
}
}
Why I am getting a null pointer exception when I add a label to stackview?
Any help will be much appreciated.
Change
cell.nameStackView!.addSubview(nameLabel)
To
cell.nameStackView!.addArrangedSubview(nameLabel)
You can use below code to add UIStackView as per your need.
let titleLabel = UILabel()
let subtitleLabel = UILabel()
lazy var titleStackView: UIStackView = {
titleLabel.textAlignment = .center
titleLabel.text = "Good Morning"
titleLabel.textColor = UIColor.white
titleLabel.font = UIFont(name: "ProximaNova-Regular", size: 12.0)
subtitleLabel.textAlignment = .center
subtitleLabel.text = "--"
subtitleLabel.textColor = UIColor.white
subtitleLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
subtitleLabel.font = UIFont(name: "DublinSpurs", size: 20.0)
let stackView = UIStackView(arrangedSubviews: [subtitleLabel])
stackView.axis = .vertical/.horizontal
return stackView
}()
Also try replacing
for name in names
{
let nameLabel=UILabel()
nameLabel.text=name
cell.nameStackView!.addSubview(nameLabel)
}
with
let nameLabel=UILabel()
nameLabel.text = names[indexPath.row]
cell.nameStackView!.addSubview(nameLabel)
If you have nameStackView in storyboard or xib, make sure the IBOutlet is connected properly. If you have created the nameStackView programmatically make sure it is initialised.
And remove all existing labels from the stackview, else you'll have duplicate labels on each scroll
class ViewCell: UITableViewCell {
//Make sure IBOutlet is connected properly
#IBOutlet weak var nameStackView: UIStackView!
}
ViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ViewCell
for name in names
{
cell.nameStackView.arrangedSubviews.forEach { cell.nameStackView.removeArrangedSubview($0) }
let nameLabel=UILabel()
nameLabel.text=name
cell.nameStackView.addSubview(nameLabel)
}
return cell
}
Try below code
let nameLabel = UILabel()
cell.nameStackView.addArrangedSubview(nameLabel)
Try initializing the stackview and label in your ViewCell class.
class ViewCell: UITableViewCell {
let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
return label
}()
let nameStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
return stackView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super .init(style: style, reuseIdentifier: reuseIdentifier)
self.configureStackView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureStackView()
{
addSubview(nameStackView)
nameStackView.translatesAutoresizingMaskIntoConstraints = false
nameStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
nameStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
nameStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
nameStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
nameStackView.addArrangedSubview(nameLabel)
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.topAnchor.constraint(equalTo: nameStackView.topAnchor,
constant:10).isActive = true
nameLabel.leadingAnchor.constraint(equalTo: nameStackView.leadingAnchor,
constant:10).isActive = true
nameLabel.trailingAnchor.constraint(equalTo: nameStackView.trailingAnchor,
constant:-10).isActive = true
nameLabel.bottomAnchor.constraint(equalTo: nameStackView.bottomAnchor,
constant:-10).isActive = true
}
}
This code is in case you didn't create your stackview in storyboard.
Now in your ViewController class, add the following code in your cellForRowAt method.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell
{
let cell=tableView.dequeueReusableCell(withIdentifier: "cell") as! ViewCell
cell.nameLabel.text = names[indexPath.row]
return cell
}
Hope, this solution works for you.

How to create custom cells 100% programmatically in Swift?

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)
}

Adding double tap gesture recognizer to UIImageView in an UITableViewCell Swift 4+

(Edited with working solution)
So I'm trying to add a double tap gesture to an UIImageView I created in a custom UITableViewCell but can't seem to get it working.
Here is my custom UITableViewCell:
protocol CustomCellDelegate: class {
func didTapImage()
}
class CustomCell: UITableViewCell {
//change let to lazy var
lazy var userImage: UIImageView = {
let newView = UIIMageView()
newView.layer.cornerRadius = 24
newView.layer.masksToBounds = true
newView.image = UIImage(named: "samplePic")
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(myFunc))
doubleTap.numberOfTouchesRequired = 1
doubleTap.numberOfTapsRequired = 2
newView.addGestureRecognizer(doubleTap)
newView.translatesAutoresizingMaskIntoConstraints = false
return newView
}
weak var delegate: CustomCellDelegate?
#objc func myFunc() {
delegate?.didTapImage()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subTitle, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
//changed addSubView(userImage) to self.contentView.addSubView(userImage)
self.contentView.addSubView(userImage)
NSLayoutConstraint.activate([
userImage.centerYAnchor.constraint(equalTo: self.centerYAnchor),
userImage.leftAnchor.constraint(equalTo: self.leftAnchor),
userImage.widthAnchor.constraint(equalToConstant: 48),
userImage.heightAnchor.constraint(equalToConstant: 48),
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Here is my custom UITableViewController:
class customTableViewController: UITableViewController, CustomCellDelegate {
fileprivate let cellId = "cellId"
func didTapImage() {
print("Tapped Image")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: cellId)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 72
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomCell
cell.delegate = self
return cell
}
}
Any ideas as to why this isn't working? What am I doing wrong? Also how do I avoid having the same tap gestures recognizer added multiple times as cells are dequeue?
You may need
userImage.translatesAutoresizingMaskIntoConstraints = false
as you create constraints programmatically
lazy var userImage: UIImageView = {
let newView = UIIMageView()
userImage.translatesAutoresizingMaskIntoConstraints = false
newView.layer.cornerRadius = 24
newView.layer.masksToBounds = true
newView.image = UIImage(named: "samplePic")
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(myFunc))
doubleTap.numberOfTouchesRequired = 1
doubleTap.numberOfTapsRequired = 2
newView.addGestureRecognizer(doubleTap)
return newView
}()
also make it a lazy var not a computed property for being 1 instance every access , add the imageView to
self.contentView.addSubView(userImage)
and set the constraints with it

UIKit - Swift - Allow tableHeaderView to scroll up, but not down

I have a UIViewController with a navigation bar and a tab bar. Other than that, the whole screen is made up of a UITableView.
I have a large tableHeaderView that has the same background color as the navbar.
When I drag the content up (scrolling down) everything looks fine.
But if I drag it up, there is an ugly disconnection between the navigation bar and the header view.
Is there any way I could anchor it to the top when dragging down, while allowing it to scroll when dragging up?
You can try creating a view and placing it behind the tableView, as the table view scrolls, the height of the view is updated.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableView : UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
let backView : UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
var backViewHeight : NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "ViewController"
self.view.addSubview(backView)
backView.translatesAutoresizingMaskIntoConstraints = false
backView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
backView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
backView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
backViewHeight = backView.heightAnchor.constraint(equalToConstant: 0)
backViewHeight?.isActive = true
self.view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.register(Cell.self, forCellReuseIdentifier: "cell")
tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: "header")
tableView.backgroundColor = .clear
self.navigationController?.navigationBar.barTintColor = .red
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 {
backViewHeight?.constant = -scrollView.contentOffset.y
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header")
header?.contentView.backgroundColor = .red
let headerLabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: 100))
headerLabel.textAlignment = .center
headerLabel.text = "Header"
header?.addSubview(headerLabel)
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = .white
return view
}
}
class Cell: UITableViewCell {
let label : UILabel = {
let label = UILabel()
label.text = "One Label"
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = .clear
setupViews()
}
func setupViews() {
self.backgroundColor = .white
self.addSubview(label)
label.frame = self.frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class Header : UITableViewHeaderFooterView {
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If you copy paste this code in an empty project you can have a look at the behavior. Don't forget to embed the ViewController in a NavigationController. Hope it helps
1) If an unwanted white space on the top of tableview is permanent and the constraints are correct this is the solution.
the scroll view insets adjusted automatically if you disable it should remove it
if #available(iOS 11.0, *) {
tableView.contentInsetAdjustmentBehavior = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}
2) if u just have it when u pull down and it goes back to its normal state. It means is that the tableview bouncing is enabled and that is normal behaviour according to iOS documentation:
If the value of this property is true, the scroll view bounces when it encounters a boundary of the content. Bouncing visually indicates that scrolling has reached an edge of the content. If the value is false, scrolling stops immediately at the content boundary without bouncing. The default value is true.
you can uncheck the bouncing from the tableview in your storyboard or xib file. Or u can use this snippet:
tableView.bounces = false
tableView.alwaysBounceVertical = false
Note: that is not recommended to disable the scroll bouncing since it would make things feel very unnatural for iOS.
and also if you want to use pull to refresh it will not work.
So finally if u choose to not disable it you will have to change the background color of the parent of your tableview and it will solve it.
I hope that makes sense!

Resources