change height of TableViewHeader and included UILabel after button click - swift

When I want to set a new long status by pressing the button, my profileStatusLabel height and TableViewHeader height as well don't change.
P.S. sorry about my English, if there are some mistakes
**ProfileHeaderView: UIView
**
import UIKit
...
private lazy var profileStatusLabel: UILabel = {
let profileStatusLabel = UILabel()
profileStatusLabel.numberOfLines = 0
profileStatusLabel.text = "Looking for a big, young, good looking, able to cook female gorilla"
profileStatusLabel.textColor = .gray
profileStatusLabel.font = profileNameLabel.font.withSize(14)
profileStatusLabel.textAlignment = .left
profileStatusLabel.sizeToFit()
profileStatusLabel.translatesAutoresizingMaskIntoConstraints = false
return profileStatusLabel
}()
private lazy var setStatusButton: UIButton = {
let setStatusButton = UIButton()
setStatusButton.backgroundColor = .systemBlue
setStatusButton.layer.cornerRadius = 4
setStatusButton.layer.shadowOffset = CGSize(width: 4, height: 4)
setStatusButton.layer.shadowOpacity = 0.7
setStatusButton.layer.shadowRadius = 4
setStatusButton.layer.shadowColor = UIColor.black.cgColor
setStatusButton.setTitle("Set status", for: .normal)
setStatusButton.setTitleColor(.white, for: .normal)
setStatusButton.titleLabel?.font = setStatusButton.titleLabel?.font.withSize(14)
setStatusButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
setStatusButton.translatesAutoresizingMaskIntoConstraints = false
return setStatusButton
}()
private lazy var statusText: String = {
return statusText
}()
private func setupView() {
addSubview(profileImageView)
addSubview(profileNameLabel)
addSubview(profileStatusLabel)
addSubview(statusTextField)
addSubview(setStatusButton)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
profileNameLabel.topAnchor.constraint(equalTo: topAnchor, constant: 27),
profileNameLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
profileNameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
profileNameLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 30),
profileStatusLabel.topAnchor.constraint(equalTo: profileNameLabel.bottomAnchor, constant: 10),
profileStatusLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
profileStatusLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
profileStatusLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 49),
statusTextField.topAnchor.constraint(equalTo: profileStatusLabel.bottomAnchor, constant: 10),
statusTextField.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
statusTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
statusTextField.heightAnchor.constraint(equalToConstant: 40),
setStatusButton.topAnchor.constraint(equalTo: statusTextField.bottomAnchor, constant: 16),
setStatusButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
setStatusButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
setStatusButton.heightAnchor.constraint(equalToConstant: 50),
bottomAnchor.constraint(equalTo: setStatusButton.bottomAnchor, constant: 16),
])
}
#objc private func statusTextChanged(_ textField: UITextField) {
statusText = statusTextField.text!
}
#objc private func buttonAction() {
guard statusTextField.text != nil else {
print("Text the status before press the button")
return
}
profileStatusLabel.text = statusText
profileStatusLabel.updateConstraintsIfNeeded()
self.setNeedsUpdateConstraints()
self.layoutIfNeeded()
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .systemGray4
setupView()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
**MyCustomHeader: UITableViewHeaderFooterView
**
import UIKit
class MyCustomHeader: UITableViewHeaderFooterView {
var profileHeaderView = ProfileHeaderView()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureContents() {
contentView.addSubview(profileHeaderView)
profileHeaderView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(equalTo: profileHeaderView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: profileHeaderView.trailingAnchor),
contentView.widthAnchor.constraint(equalTo: profileHeaderView.widthAnchor),
contentView.heightAnchor.constraint(equalTo: profileHeaderView.heightAnchor),
contentView.topAnchor.constraint(equalTo: profileHeaderView.topAnchor),
])
}
}
**ProfileViewController
**
import UIKit
class ProfileViewController: UIViewController, UIGestureRecognizerDelegate {
let postList = [robberyPost, eatingPost, elephantPost, camelPost]
var profileTableView = UITableView()
let myCustomHeader = MyCustomHeader()
let headerID = "headerId"
let headerID2 = "headerId2"
let cellID = "cellId"
let collectionCellID = "collectionCellId"
...
func setupTableView() {
profileTableView.contentInsetAdjustmentBehavior = .never
profileTableView.register(PhotosTableViewCell.self, forCellReuseIdentifier: PhotosTableViewCell.cellID)
profileTableView.register(PostTableViewCell.self, forCellReuseIdentifier: PostTableViewCell.cellID)
profileTableView.delegate = self
profileTableView.dataSource = self
profileTableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileTableView)
}
func setupMyCustomHeader() {
profileTableView.register(MyCustomHeader.self, forHeaderFooterViewReuseIdentifier: headerID)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
profileTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
profileTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
profileTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
profileTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
...
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray6
setupTableView()
setupMyCustomHeader()
setupConstraints()
}
}
extension ProfileViewController: UITableViewDelegate, UITableViewDataSource {
// MARK: - Table view data source
...
// MARK: - Table view delegate
...
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if #available(iOS 15, *) {
tableView.sectionHeaderTopPadding = 0
}
if section == 0 {
return UITableView.automaticDimension
} else {
return 50
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerID) as! MyCustomHeader
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(profileImageViewClicked(_ :)))
tapRecognizer.numberOfTapsRequired = 1
tapRecognizer.numberOfTouchesRequired = 1
tapRecognizer.delegate = self
header.profileHeaderView.profileImageView.isUserInteractionEnabled = true
header.profileHeaderView.profileImageView.addGestureRecognizer(tapRecognizer)
return header
} else {
let header2 = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerID2) as! OneMoreCustomHeader
return header2
}
}
...
}
Probably I should use updateConstraints() function, but can't find the right way.
I added this code to buttonAction, buy it doesn't work
profileStatusLabel.updateConstraintsIfNeeded()
self.setNeedsUpdateConstraints()
self.layoutIfNeeded()

Any time you change the height of a table view element - cell, section header or footer view, etc - you must tell the table view to re-layout its elements.
This is commonly done with a closure.
For example, you would add this property to your custom header view class:
// closure so table layout can be updated
var contentChanged: (() -> ())?
and then set that closure in viewForHeaderInSection:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerID) as! MyCustomHeader
// set the closure
header.contentChanged = {
// tell the table view to re-layout itself so the header height can be changed
tableView.performBatchUpdates(nil)
}
return header
} else {
let header2 = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerID2) as! OneMoreCustomHeader
return header2
}
}
and when you tap the "Set status" button to update the profileStatusLabel, you would use the closure to "call back" to the controller:
contentChanged?()
With the code you posted, you are embedding ProfileHeaderView in MyCustomHeader, which complicates things a little because you will need a closure in MyCustomHeader that works with another closure in ProfileHeaderView.
There really is no need to do that -- you can put all of your UI elements directly in MyCustomHeader to avoid that issue.
Here is a complete, runnable example. I changed your MyCustomHeader as described... as well as added some other code that you will probably end up needing (see the comments):
class MyCustomHeader: UITableViewHeaderFooterView {
// closure to inform the controller the status text changed
// so we can update the data and
// so the table layout can be updated
var contentChanged: ((String) -> ())?
// presumably, we'll be setting the statusText and the name from a dataSource
public var statusText: String = "" {
didSet {
profileStatusLabel.text = statusText
}
}
public var name: String = "" {
didSet {
profileNameLabel.text = name
}
}
private lazy var profileImageView: UIImageView = {
let v = UIImageView()
if let img = UIImage(systemName: "person.crop.circle") {
v.image = img
}
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
private lazy var profileNameLabel: UILabel = {
let profileStatusLabel = UILabel()
profileStatusLabel.numberOfLines = 0
profileStatusLabel.text = ""
profileStatusLabel.textColor = .black
profileStatusLabel.textAlignment = .left
profileStatusLabel.translatesAutoresizingMaskIntoConstraints = false
return profileStatusLabel
}()
private lazy var profileStatusLabel: UILabel = {
let profileStatusLabel = UILabel()
profileStatusLabel.numberOfLines = 0
profileStatusLabel.text = ""
profileStatusLabel.textColor = .gray
profileStatusLabel.textAlignment = .left
profileStatusLabel.translatesAutoresizingMaskIntoConstraints = false
return profileStatusLabel
}()
private lazy var setStatusButton: UIButton = {
let setStatusButton = UIButton()
setStatusButton.backgroundColor = .systemBlue
setStatusButton.layer.cornerRadius = 4
setStatusButton.layer.shadowOffset = CGSize(width: 4, height: 4)
setStatusButton.layer.shadowOpacity = 0.7
setStatusButton.layer.shadowRadius = 4
setStatusButton.layer.shadowColor = UIColor.black.cgColor
setStatusButton.setTitle("Set status", for: .normal)
setStatusButton.setTitleColor(.white, for: .normal)
setStatusButton.setTitleColor(.lightGray, for: .highlighted)
setStatusButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
setStatusButton.translatesAutoresizingMaskIntoConstraints = false
return setStatusButton
}()
private lazy var statusTextField: UITextField = {
let statusTextField = UITextField()
statusTextField.text = ""
statusTextField.borderStyle = .roundedRect
statusTextField.backgroundColor = .white
statusTextField.translatesAutoresizingMaskIntoConstraints = false
return statusTextField
}()
private func setupView() {
contentView.addSubview(profileImageView)
contentView.addSubview(profileNameLabel)
contentView.addSubview(profileStatusLabel)
contentView.addSubview(statusTextField)
contentView.addSubview(setStatusButton)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
profileImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
profileImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
profileImageView.widthAnchor.constraint(equalToConstant: 160.0),
profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
profileNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 27),
profileNameLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
profileNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
profileNameLabel.heightAnchor.constraint(equalToConstant: 30),
profileStatusLabel.topAnchor.constraint(equalTo: profileNameLabel.bottomAnchor, constant: 10),
profileStatusLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
profileStatusLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
profileStatusLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 49),
statusTextField.topAnchor.constraint(equalTo: profileStatusLabel.bottomAnchor, constant: 10),
statusTextField.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
statusTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
statusTextField.heightAnchor.constraint(equalToConstant: 40),
setStatusButton.topAnchor.constraint(equalTo: statusTextField.bottomAnchor, constant: 16),
setStatusButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
setStatusButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
setStatusButton.heightAnchor.constraint(equalToConstant: 50),
contentView.bottomAnchor.constraint(equalTo: setStatusButton.bottomAnchor, constant: 16),
])
}
#objc private func buttonAction() {
guard let stText = statusTextField.text else {
print("Text the status before press the button")
return
}
statusTextField.resignFirstResponder()
// update statusText property
// which will also set the text in the label
statusText = stText
// call the closure, passing back the new text
contentChanged?(stText)
}
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .systemGray4
setupView()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ProfileViewController: UIViewController, UIGestureRecognizerDelegate {
// presumably, this will be loaded from saved data, along with the rest of the table data
var dataSourceStatusText: String = "Looking for a big, young, good looking, able to cook female gorilla"
var profileTableView = UITableView()
let headerID = "headerId"
let cellID = "cellId"
func setupTableView() {
profileTableView.contentInsetAdjustmentBehavior = .never
profileTableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
profileTableView.delegate = self
profileTableView.dataSource = self
profileTableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileTableView)
}
func setupMyCustomHeader() {
profileTableView.register(MyCustomHeader.self, forHeaderFooterViewReuseIdentifier: headerID)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
profileTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
profileTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
profileTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
profileTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray6
setupTableView()
setupMyCustomHeader()
setupConstraints()
}
}
extension ProfileViewController: UITableViewDelegate, UITableViewDataSource {
// let's use 10 sections each with 5 rows so we can scroll the header out-of-view
func numberOfSections(in tableView: UITableView) -> Int {
return 10
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if #available(iOS 15, *) {
tableView.sectionHeaderTopPadding = 0
}
if section == 0 {
return UITableView.automaticDimension
} else {
return 50
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerID) as! MyCustomHeader
header.name = "#KillaGorilla"
header.statusText = dataSourceStatusText
// set the closure
header.contentChanged = { [weak self] newStatus in
guard let self = self else { return }
// update the status text (probably also saving it somewhere?)
// if we don't do this, and the section header scrolls out of view,
// the *original* status text will be shown
self.dataSourceStatusText = newStatus
// tell the table view to re-layout itself so the header height can be changed
tableView.performBatchUpdates(nil)
}
return header
} else {
// this would be your other section header view
// for now, let's just use a label
let v = UILabel()
v.backgroundColor = .yellow
v.text = "Section Header: \(section)"
return v
}
}
}
Give that a try.

Related

Simulate a cell like in between two cells and below two cells

I am using tableview and collection view inside to create this screen.
If you look closer in the design..i will have to create an effect of cells going above below or top cell with a corner radius...which simulates like the cells are below and in between two cells...
The same goes for the top area which has a corner radius.
Please guide through a proper way to design this entire screen.
(I am using storyboard and UIKit only...i dont want to use swiftUI)
Various ways to approach this -- here's one...
Four cell types:
"Top" cell
"Categories" cell
"Top Products" cell
"Suggestions" cell
In each cell class, add a UIView with rounded corners - this will hold the "content" of each cell.
Behind the rounded corners view, add a "Top Color" UIView and a a "Bottom Color" UIView.
It looks like this:
When running, we get this result:
Here is some sample code:
class MyBaseCell: UITableViewCell {
var topBotColors: [UIColor] = [.white, .white] {
didSet {
topColorView.backgroundColor = topBotColors[0]
botColorView.backgroundColor = topBotColors[1]
}
}
let theStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 12
return v
}()
let topColorView = UIView()
let botColorView = UIView()
let roundedCornerView = UIView()
var topConstraint: NSLayoutConstraint!
var botConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
[topColorView, botColorView, roundedCornerView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(v)
}
theStack.translatesAutoresizingMaskIntoConstraints = false
roundedCornerView.addSubview(theStack)
let g = contentView.layoutMarginsGuide
topConstraint = theStack.topAnchor.constraint(equalTo: roundedCornerView.topAnchor, constant: 12.0)
botConstraint = theStack.bottomAnchor.constraint(equalTo: roundedCornerView.bottomAnchor, constant: -12.0)
NSLayoutConstraint.activate([
topColorView.topAnchor.constraint(equalTo: contentView.topAnchor),
topColorView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
topColorView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
botColorView.topAnchor.constraint(equalTo: topColorView.bottomAnchor),
botColorView.heightAnchor.constraint(equalTo: topColorView.heightAnchor),
botColorView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
botColorView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
botColorView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
roundedCornerView.topAnchor.constraint(equalTo: g.topAnchor),
roundedCornerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
roundedCornerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
roundedCornerView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
theStack.leadingAnchor.constraint(equalTo: roundedCornerView.leadingAnchor),
theStack.trailingAnchor.constraint(equalTo: roundedCornerView.trailingAnchor),
topConstraint, botConstraint,
])
self.backgroundColor = .clear
contentView.backgroundColor = .clear
}
}
class TopCell: MyBaseCell {
override func commonInit() {
super.commonInit()
// let's add 1 tall label
let v = UILabel()
v.textAlignment = .center
v.text = "Top Cell"
v.textColor = .white
v.font = .systemFont(ofSize: 24.0, weight: .bold)
theStack.addArrangedSubview(v)
// avoid auot-layout complaints
let c = v.heightAnchor.constraint(equalToConstant: 80.0)
c.priority = .required - 1
c.isActive = true
}
}
class CatsCell: MyBaseCell {
override func commonInit() {
super.commonInit()
// let's add a few labels
for i in 1...4 {
let v = UILabel()
v.textAlignment = .center
v.text = "Categories Label \(i)"
theStack.addArrangedSubview(v)
}
roundedCornerView.backgroundColor = .white
roundedCornerView.layer.cornerRadius = 24
}
}
class TopProdsCell: MyBaseCell {
override func commonInit() {
super.commonInit()
// let's add a few labels
for i in 1...3 {
let v = UILabel()
v.textAlignment = .center
v.text = "Top Prods Label \(i)"
v.textColor = .white
theStack.addArrangedSubview(v)
}
roundedCornerView.backgroundColor = .clear
// increase top/bottom "spacing"
topConstraint.constant = 24.0
botConstraint.constant = -24.0
}
}
class SuggestionsCell: MyBaseCell {
override func commonInit() {
super.commonInit()
// let's add a few labels
for i in 1...6 {
let v = UILabel()
v.textAlignment = .center
v.text = "Suggested Label \(i)"
theStack.addArrangedSubview(v)
}
roundedCornerView.backgroundColor = .white
roundedCornerView.layer.cornerRadius = 24
}
}
class SampleTableVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
let myGray: UIColor = .gray
let myBlue: UIColor = UIColor(red: 0.25, green: 0.30, blue: 0.65, alpha: 1.0)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
tableView.register(TopCell.self, forCellReuseIdentifier: "topCell")
tableView.register(CatsCell.self, forCellReuseIdentifier: "catCell")
tableView.register(TopProdsCell.self, forCellReuseIdentifier: "prodCell")
tableView.register(SuggestionsCell.self, forCellReuseIdentifier: "suggCell")
tableView.dataSource = self
tableView.delegate = self
let bkv = UIView()
bkv.backgroundColor = myGray
tableView.backgroundView = bkv
tableView.separatorStyle = .none
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let c = tableView.dequeueReusableCell(withIdentifier: "topCell", for: indexPath) as! TopCell
c.topBotColors = [myGray, myGray]
return c
}
if indexPath.row == 1 {
let c = tableView.dequeueReusableCell(withIdentifier: "catCell", for: indexPath) as! CatsCell
c.topBotColors = [myGray, myBlue]
return c
}
if indexPath.row == 2 {
let c = tableView.dequeueReusableCell(withIdentifier: "prodCell", for: indexPath) as! TopProdsCell
c.topBotColors = [myBlue, myBlue]
return c
}
let c = tableView.dequeueReusableCell(withIdentifier: "suggCell", for: indexPath) as! SuggestionsCell
c.topBotColors = [myBlue, myGray]
return c
}
}

Table View Design overlapping

I am new to swift .I want to display the records with image view in table view cell . I have defined the property with leadingAnchor , trailingAnchor, widthAnchor, heightAnchor with content view . But when I run the app it overlapping the view .
Here is the code in cell .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the result .
This is rather tricky because it seems your constraints are fine, assuming that your tableview height is 100, but the screenshot tableview cells seem a little shorter than 100. Let's assume the cell height is 100 correct.
I suggest you try configuring the imageView (and other views) in override func layoutSubViews(), which is a function that renders whenever the contentView's bound change. It should also be noted that better practice is where the imageSize is relative to the cell/contentView's frame instead of hardcoded values.
So it should look like
import UIKit
class PeopleCell: UITableViewCell {
let profileImageView:UIImageView = {
let img = UIImageView()
return img
}()
override func layoutSubviews() {
super.layoutSubviews()
profileImageView.translatesAutoresizingMaskIntoConstraints = false profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
//You may want to try with other type of contentMode such as aspectFit, etc
profileImageView.contentMode = .scaleAspectFill
profileImageView.layer.cornerRadius = self.frame.width / 2
profileImageView.clipsToBounds = true
}
//If above doesn't work, you may want to look into the imageConfiguration function you made and ensure that contentMode is applied properly.
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
self?.profileImageView.contentMode = .scaleAspectFill
}
}
If all of the above code doesn't work, try to find the profileImageView size values by using breakpoints or ViewHierarchy within Xcode. To check the height of image or cell itself, they should be sufficient for you to find clues to resolve the issue.
All the best.

UITextView with Adjustable Font Size

I tried to make an editable uitextview with centered text that can have a maximum of 2 lines and that automatically adjusts its font size in order to fit its text within a fixed width and height.
My solution: Type some text in a UITextView, automatically copy that text and paste it in a uilabel that itself automatically adjusts its font size perfectly, and then retrieve the newly adjusted font size of the uilabel and set that size on the UITextView text.
I have spent about a month on this and repeatedly failed. I can't find a way to make it work. My attempted textview below glitches some letters out and hides large portions of out-of-bounds text instead of resizing everything. Please help me stack overflow Gods.
My attempt:
import UIKit
class TextViewViewController: UIViewController{
private let editableUITextView: UITextView = {
let tv = UITextView()
tv.font = UIFont.systemFont(ofSize: 20)
tv.text = "Delete red text, and type here"
tv.backgroundColor = .clear
tv.textAlignment = .center
tv.textContainer.maximumNumberOfLines = 2
tv.textContainer.lineBreakMode = .byWordWrapping
tv.textColor = .red
return tv
}()
private let correctTextSizeLabel: UILabel = {
let tv = UILabel()
tv.font = UIFont.systemFont(ofSize: 20)
tv.backgroundColor = .clear
tv.text = "This is properly resized"
tv.adjustsFontSizeToFitWidth = true
tv.lineBreakMode = .byTruncatingTail
tv.numberOfLines = 2
tv.textAlignment = .center
tv.textColor = .green
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(correctTextSizeLabel)
view.addSubview(editableUITextView)
editableUITextView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
editableUITextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
editableUITextView.heightAnchor.constraint(equalToConstant: 150).isActive = true
editableUITextView.widthAnchor.constraint(equalToConstant: 150).isActive = true
editableUITextView.translatesAutoresizingMaskIntoConstraints = false
editableUITextView.delegate = self
correctTextSizeLabel.leftAnchor.constraint(equalTo: editableUITextView.leftAnchor).isActive = true
correctTextSizeLabel.rightAnchor.constraint(equalTo: editableUITextView.rightAnchor).isActive = true
correctTextSizeLabel.topAnchor.constraint(equalTo: editableUITextView.topAnchor).isActive = true
correctTextSizeLabel.bottomAnchor.constraint(equalTo: editableUITextView.bottomAnchor).isActive = true
correctTextSizeLabel.translatesAutoresizingMaskIntoConstraints = false
editableUITextView.isScrollEnabled = false
}
func getApproximateAdjustedFontSizeOfLabel(label: UILabel) -> CGFloat {
if label.adjustsFontSizeToFitWidth == true {
var currentFont: UIFont = label.font
let originalFontSize = currentFont.pointSize
var currentSize: CGSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: currentFont])
while currentSize.width > label.frame.size.width * 2 && currentFont.pointSize > (originalFontSize * label.minimumScaleFactor) {
currentFont = currentFont.withSize(currentFont.pointSize - 1)
currentSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: currentFont])
}
return currentFont.pointSize
} else {
return label.font.pointSize
}
}
}
//MARK: - UITextViewDelegate
extension TextViewViewController : UITextViewDelegate {
private func textViewShouldBeginEditing(_ textView: UITextView) {
textView.becomeFirstResponder()
}
func textViewDidBeginEditing(_ textView: UITextView) {
}
func textViewDidEndEditing(_ textView: UITextView) {
}
func textViewDidChange(_ textView: UITextView) {
textView.becomeFirstResponder()
self.correctTextSizeLabel.text = textView.text
self.correctTextSizeLabel.isHidden = false
let estimatedTextSize = self.getApproximateAdjustedFontSizeOfLabel(label: self.correctTextSizeLabel)
print("estimatedTextSize: ",estimatedTextSize)
self.editableUITextView.font = UIFont.systemFont(ofSize: estimatedTextSize)
}
}
UITextField's have the option to automatically adjust font size to fit a fixed width but they only allow 1 line of text, I need it to have a maximum of 2. UILabel's solve this problem perfectly but they aren't editable.
After some searching, this looks like it will be very difficult to get working as desired.
This doesn't directly answer your question, but it may be an option:
Here's the example code:
class TestInputViewController: UIViewController {
let testLabel: InputLabel = InputLabel()
override func viewDidLoad() {
super.viewDidLoad()
let instructionLabel = UILabel()
instructionLabel.textAlignment = .center
instructionLabel.text = "Tap yellow label to edit..."
let centeringFrameView = UIView()
// label properties
let fnt: UIFont = .systemFont(ofSize: 32.0)
testLabel.isUserInteractionEnabled = true
testLabel.font = fnt
testLabel.adjustsFontSizeToFitWidth = true
testLabel.minimumScaleFactor = 0.25
testLabel.numberOfLines = 2
testLabel.setContentHuggingPriority(.required, for: .vertical)
let minLabelHeight = ceil(fnt.lineHeight)
// so we can see the frames
centeringFrameView.backgroundColor = .red
testLabel.backgroundColor = .yellow
[centeringFrameView, instructionLabel, testLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(instructionLabel)
view.addSubview(centeringFrameView)
centeringFrameView.addSubview(testLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// instruction label centered at top
instructionLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
instructionLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// centeringFrameView 20-pts from instructionLabel bottom
centeringFrameView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 20.0),
// Leading / Trailing with 20-pts "padding"
centeringFrameView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
centeringFrameView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// test label centered vertically in centeringFrameView
testLabel.centerYAnchor.constraint(equalTo: centeringFrameView.centerYAnchor, constant: 0.0),
// Leading / Trailing with 20-pts "padding"
testLabel.leadingAnchor.constraint(equalTo: centeringFrameView.leadingAnchor, constant: 20.0),
testLabel.trailingAnchor.constraint(equalTo: centeringFrameView.trailingAnchor, constant: -20.0),
// height will be zero if label has no text,
// so give it a min height of one line
testLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: minLabelHeight),
// centeringFrameView height = 3 * minLabelHeight
centeringFrameView.heightAnchor.constraint(equalToConstant: minLabelHeight * 3.0)
])
// to handle user input
testLabel.editCallBack = { [weak self] str in
guard let self = self else { return }
self.testLabel.text = str
}
testLabel.doneCallBack = { [weak self] in
guard let self = self else { return }
// do something when user taps done / enter
}
let t = UITapGestureRecognizer(target: self, action: #selector(self.labelTapped(_:)))
testLabel.addGestureRecognizer(t)
}
#objc func labelTapped(_ g: UITapGestureRecognizer) -> Void {
testLabel.becomeFirstResponder()
testLabel.inputContainerView.theTextView.text = testLabel.text
testLabel.inputContainerView.theTextView.becomeFirstResponder()
}
}
class InputLabel: UILabel {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
lazy var inputContainerView: CustomInputAccessoryView = {
let customInputAccessoryView = CustomInputAccessoryView(frame: .zero)
customInputAccessoryView.backgroundColor = .blue
customInputAccessoryView.editCallBack = { [weak self] str in
guard let self = self else { return }
self.editCallBack?(str)
}
customInputAccessoryView.doneCallBack = { [weak self] in
guard let self = self else { return }
self.resignFirstResponder()
}
return customInputAccessoryView
}()
}
class CustomInputAccessoryView: UIView, UITextViewDelegate {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
let theTextView: UITextView = {
let tv = UITextView()
tv.isScrollEnabled = false
tv.font = .systemFont(ofSize: 16)
tv.autocorrectionType = .no
tv.returnKeyType = .done
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(theTextView)
theTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constraint text view with 8-pts "padding" on all 4 sides
theTextView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
theTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
theTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
theTextView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
])
theTextView.delegate = self
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
doneCallBack?()
}
return true
}
func textViewDidChange(_ textView: UITextView) {
editCallBack?(textView.text ?? "")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}

Swift - Size of content view does not match with UITableViewCell

I have an iOS app with a TableView, all UI of the app are done programmingly with autolayout using Swift.
All UI are working great until recently, I have to add a new component (custom UIView) inside the UITableViewCell which cell height will be changed when the new component is shown or hidden. The height of the cell is not correct so my views inside the UITableViewCell become a mess.
After checking the Debug View Hierarchy, I found that the height of the UITableViewCell is different than then UITableViewCellContentView.
When the component should display:
Table view cell has correct height (Longer height)
Content View of UITableViewCell is shorter then expected (the height is correct if component is hidden)
When the component should hidden:
Table view cell has correct height (Shorter height)
Content View of UITableViewCell is longer then expected (the height is correct if component is display)
I am not really sure what is the real issue. When I toggle the component to be displayed or not, I do the followings:
// Update the constraints status
var componentIsShown: Bool = .....
xxxConstraints?.isActive = componentIsShown
yyyConstraints?.isActive = !componentIsShown
// Update UI
layoutIfNeeded()
view.setNeedsUpdateConstraints()
tableView.beginUpdates()
tableView.endUpdates()
It seems to me that when I toggle the component to be displayed or not, the UITableViewCell is updated immediately, but content view used previous data to update the height. If this is the issue, how could I update the content view height also?
If this is not the issue, any suggestion to fix it or do further investigation?
Thanks
====================
Updated in 2018-08-29:
Attached are the codes for the issue.
Clicking on the topMainContainerView in MyBaseView (the view with red alpha bg) will toggle the hiddenView display or not.
In ViewController.swift:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell: MyViewCell
if let theCell = tableView.dequeueReusableCell(withIdentifier: "cell") as? MyViewCell
{
cell = theCell
}
else
{
cell = MyViewCell(reuseIdentifier: "cell")
}
cell.setViewCellDelegate(delegate: self)
return cell
}
MyViewCell.swift
class MyViewCell: MyViewParentCell
{
var customView: MyView
required init?(coder aDecoder: NSCoder)
{
return nil
}
override init(reuseIdentifier: String?)
{
customView = MyView()
super.init(reuseIdentifier: reuseIdentifier)
selectionStyle = .none
}
override func initViews()
{
contentView.addSubview(customView)
}
override func initLayout()
{
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
customView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
}
override func setViewCellDelegate(delegate: MyViewCellDelegate)
{
super.setViewCellDelegate(delegate: delegate)
customView.delegate = delegate
customView.innerDelegate = self
}
}
MyViewParentCell.swift:
protocol MyViewCellDelegate
{
func reloadTableView()
}
protocol MyViewCellInnerDelegate
{
func viewCellLayoutIfNeeded()
}
class MyViewParentCell: UITableViewCell
{
private var delegate: MyViewCellDelegate?
var innerDelegate: MyViewCellInnerDelegate?
required init?(coder aDecoder: NSCoder)
{
return nil
}
init(reuseIdentifier: String?)
{
super.init(style: UITableViewCellStyle.default, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
initViews()
initLayout()
}
func initViews()
{
}
func initLayout()
{
}
override func layoutSubviews()
{
}
func setViewCellDelegate(delegate: MyViewCellDelegate)
{
self.delegate = delegate
}
}
extension MyViewParentCell: MyViewCellInnerDelegate
{
func viewCellLayoutIfNeeded()
{
print("MyViewParentCell viewCellLayoutIfNeeded")
setNeedsUpdateConstraints()
layoutIfNeeded()
}
}
MyView.swift
class MyView: MyParentView
{
private var mainView: UIView
// Variables
var isViewShow = true
// Constraint
private var mainViewHeightConstraint: NSLayoutConstraint?
private var hiddenViewHeightConstraint: NSLayoutConstraint?
private var hiddenViewPosYHideViewConstraint: NSLayoutConstraint?
private var hiddenViewPosYShowViewConstraint: NSLayoutConstraint?
// Constant:
let viewSize = UIScreen.main.bounds.width
// Init
override init()
{
mainView = UIView(frame: CGRect(x: 0, y: 0, width: viewSize, height: viewSize))
super.init()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
override func initViews()
{
super.initViews()
topMainContainerView.addSubview(mainView)
}
override func initLayout()
{
super.initLayout()
//
mainView.translatesAutoresizingMaskIntoConstraints = false
mainView.topAnchor.constraint(equalTo: topMainContainerView.topAnchor).isActive = true
mainView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor).isActive = true
mainView.leadingAnchor.constraint(equalTo: topMainContainerView.leadingAnchor).isActive = true
mainView.widthAnchor.constraint(equalToConstant: viewSize).isActive = true
mainView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true
mainViewHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: viewSize)
mainViewHeightConstraint?.isActive = true
}
override func toggle()
{
isViewShow = !isViewShow
print("toggle: isViewShow is now (\(isViewShow))")
setViewHidden()
}
private func setViewHidden()
{
UIView.animate(withDuration: 0.5) {
if self.isViewShow
{
self.hiddenViewBottomConstraint?.isActive = false
self.hiddenViewTopConstraint?.isActive = true
}
else
{
self.hiddenViewTopConstraint?.isActive = false
self.hiddenViewBottomConstraint?.isActive = true
}
self.layoutIfNeeded()
self.needsUpdateConstraints()
self.innerDelegate?.viewCellLayoutIfNeeded()
self.delegate?.reloadTableView()
}
}
}
MyParentView.swift
class MyParentView: MyBaseView
{
var delegate: MyViewCellDelegate?
var innerDelegate: MyViewCellInnerDelegate?
override init()
{
super.init()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
override func initViews()
{
super.initViews()
}
override func initLayout()
{
super.initLayout()
translatesAutoresizingMaskIntoConstraints = false
}
}
MyBaseView.swift
class MyBaseView: UIView
{
var topMainContainerView: UIView
var hiddenView: UIView
var bottomActionContainerView: UIView
var bottomSeparator: UIView
var hiddenViewTopConstraint: NSLayoutConstraint?
var hiddenViewBottomConstraint: NSLayoutConstraint?
// Layout constratint
var descriptionWidthConstraint: NSLayoutConstraint?
var moreMainTopAnchorConstraint: NSLayoutConstraint?
var moreMainBottomAnchorConstraint: NSLayoutConstraint?
var separatorTopAnchorToActionBarConstraint: NSLayoutConstraint?
var separatorTopAnchorToPartialCommentConstraint: NSLayoutConstraint?
// Constant
let paddingX: CGFloat = 10
let InnerPaddingY: CGFloat = 9
init()
{
topMainContainerView = UIView()
hiddenView = UIView()
bottomActionContainerView = UIView()
bottomSeparator = UIView()
super.init(frame: .zero)
initViews()
initLayout()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
func initViews()
{
let borderColor = UIColor.gray
backgroundColor = UIColor(red: 211/255.0, green: 211/255.0, blue: 1, alpha: 1)
topMainContainerView.backgroundColor = UIColor.red.withAlphaComponent(0.7)
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
topMainContainerView.addGestureRecognizer(gesture)
// Hidden View
hiddenView.backgroundColor = UIColor.yellow
hiddenView.layer.cornerRadius = 50
// Action
bottomActionContainerView.backgroundColor = UIColor.blue
bottomSeparator.backgroundColor = borderColor
// Add hiddenView first, so it will hide behind main view
addSubview(hiddenView)
addSubview(topMainContainerView)
addSubview(bottomActionContainerView)
addSubview(bottomSeparator)
}
func initLayout()
{
// MARK: Main
topMainContainerView.translatesAutoresizingMaskIntoConstraints = false
topMainContainerView.topAnchor.constraint(equalTo: topAnchor, constant: 30).isActive = true
topMainContainerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
topMainContainerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
topMainContainerView.heightAnchor.constraint(equalToConstant: 150).isActive = true
// Hidden View
hiddenView.translatesAutoresizingMaskIntoConstraints = false
hiddenViewTopConstraint = hiddenView.topAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
hiddenViewTopConstraint?.isActive = true
hiddenView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
hiddenView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
hiddenViewBottomConstraint = hiddenView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
hiddenViewBottomConstraint?.isActive = false
hiddenView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// MARK: Bottom
bottomActionContainerView.translatesAutoresizingMaskIntoConstraints = false
bottomActionContainerView.topAnchor.constraint(equalTo: hiddenView.bottomAnchor, constant: InnerPaddingY).isActive = true
bottomActionContainerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
bottomActionContainerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
bottomActionContainerView.heightAnchor.constraint(equalToConstant: 32).isActive = true
// MARK: Separator
bottomSeparator.translatesAutoresizingMaskIntoConstraints = false
separatorTopAnchorToPartialCommentConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
separatorTopAnchorToActionBarConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
separatorTopAnchorToPartialCommentConstraint?.isActive = false
separatorTopAnchorToActionBarConstraint?.isActive = true
bottomSeparator.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
bottomSeparator.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
bottomSeparator.heightAnchor.constraint(equalToConstant: 10).isActive = true
bottomSeparator.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
self.hiddenViewBottomConstraint?.isActive = false
self.hiddenViewTopConstraint?.isActive = true
}
#objc func toggle()
{
}
}
The layout of contentView is not updating. You should try
cell.contentView.layoutIfNeeded()
Try and share results.
I finally found that I should call super.layoutSubviews() in MyViewParentCell.swift or simply remove the function to fix the issue.
override func layoutSubviews()
{
super.layoutSubviews()
}

cellForRowAt not being called but numberOfRowsInSection is

So, I have checked that tableview.reloadData() is being called on the main thread and that numberOfRowsInSection has values. When I look at the view hierarchy the tableView is clearly there and shows the background colour when set. What I have noticed that is odd is that the view hierarchy says its an Empty list(understandable) but it contains two UIImageView's one in the top right and one in the bottom left. Don't understand this at all. My tableView is nested in an iCarousel which I'm sure is where the issue lies. My code is entirely programmatic, so here is what i think may be of value:
iCarouselDatasource:(carousels dont have cells but views instead)
class BillCarouselDataSource: NSObject, iCarouselDataSource {
var bills = [Bill]()
func numberOfItems(in carousel: iCarousel) -> Int {
return bills.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
let bill = bills[index]
let billCarouselView = BillCarouselView(bill: bill)
billCarouselView.frame = carousel.frame
billCarouselView.setupView()
return billCarouselView
}
}
iCarousel view(cell)
class BillCarouselView: UIView {
private var bill: Bill!
private let nameLabel = CarouselLabel()
private let locationLabel = CarouselLabel()
private let dateLabel = CarouselLabel()
private let splitButton = CarouselButton()
private let tableView = UITableView()
required init(bill: Bill) {
super.init(frame: .zero)
self.bill = bill
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupView() {
setupHierarchy()
setupViews()
setupLayout()
}
private func setupHierarchy() {
addSubview(nameLabel)
addSubview(locationLabel)
addSubview(dateLabel)
addSubview(tableView)
addSubview(splitButton)
}
private func setupViews() {
accessibilityIdentifier = AccesID.carouselView
isUserInteractionEnabled = true
layer.cornerRadius = Layout.carouselViewCornerRadius
backgroundColor = Color.carouselView
nameLabel.text = bill.name
nameLabel.textAlignment = .center
nameLabel.accessibilityIdentifier = AccesID.carouselNameLabel
locationLabel.text = bill.location
locationLabel.textAlignment = .left
locationLabel.accessibilityIdentifier = AccesID.carouselLocationLabel
dateLabel.text = bill.creationDate
dateLabel.font = Font.carouselDateText
dateLabel.textAlignment = .right
dateLabel.accessibilityIdentifier = AccesID.carouselDateLabel
let dataSource = CarouselTableViewDataSource()
let delegate = CarouselTableViewDelegate()
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.dataSource = dataSource
tableView.delegate = delegate
tableView.register(CarouselTableViewCell.classForCoder(),
forCellReuseIdentifier: "carouselTableViewCell")
dataSource.items = bill.items
tableView.reloadData()
let buttonTitle = "Split £\(bill.totalPrice())"
splitButton.setTitle(buttonTitle, for: .normal)
}
private func setupLayout() {
nameLabel.pinToSuperview(edges: [.top, .left, .right])
locationLabel.pinToSuperview(edges: [.left],
constant: Layout.spacer,
priority: .required)
locationLabel.pinTop(to: nameLabel, anchor: .bottom)
locationLabel.pinRight(to: dateLabel, anchor: .left)
dateLabel.pinToSuperview(edges: [.right],
constant: -Layout.spacer,
priority: .required)
dateLabel.pinTop(to: nameLabel, anchor: .bottom)
tableView.pinTop(to: dateLabel, anchor: .bottom)
tableView.pinToSuperview(edges: [.left, .right])
tableView.pinBottom(to: splitButton, anchor: .top)
splitButton.pinToSuperview(edges: [.left, .right, .bottom])
}
}
class CarouselTableViewDataSource: NSObject, UITableViewDataSource {
var items: [Item]? {
didSet {
let duplicatedItems = items
filteredItems = duplicatedItems?.filterDuplicates { ( $0.creationID ) }
}
}
private var filteredItems: [Item]!
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredItems.count
}
//Set what each cell in the tableview contains.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CarouselTableViewCell = tableView.dequeueReusableCell(withIdentifier: "carouselTableViewCell") as! CarouselTableViewCell
let duplicateItems = items?.filter { Int($0.creationID)! == indexPath.row }
let quantity = "\(duplicateItems!.count) x "
let name = duplicateItems![0].name
let price = formatPrice(duplicateItems![0].price)
cell.quantityLabel.text = quantity
cell.nameLabel.text = name
cell.priceLabel.text = price
return cell
}
private func formatPrice(_ stringPrice: String) -> String {
var formattedPrice = stringPrice
let price = NSNumber(value: Double(stringPrice)!)
let formatter = NumberFormatter()
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: price) {
formattedPrice = "Tip Amount: \(formattedTipAmount)"
}
return formattedPrice
}
}
class CarouselTableViewCell: UITableViewCell {
var containerView = UIView()
var quantityLabel = UILabel()
var nameLabel = UILabel()
var priceLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: "carouselTableViewCell")
setupHierarchy()
setupViews()
setupLayout()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
private func setupHierarchy() {
containerView.addSubview(quantityLabel)
containerView.addSubview(nameLabel)
containerView.addSubview(priceLabel)
containerView.addSubview(containerView)
contentView.addSubview(containerView)
}
private func setupViews() {
quantityLabel.textAlignment = .left
quantityLabel.backgroundColor = .clear
quantityLabel.textColor = Color.carouselText
nameLabel.textAlignment = .left
nameLabel.backgroundColor = .clear
nameLabel.textColor = Color.carouselText
priceLabel.textAlignment = .right
priceLabel.backgroundColor = .clear
priceLabel.textColor = Color.carouselText
}
private func setupLayout() {
containerView.pinToSuperview(edges: [.left, .right])
containerView.pinToSuperviewTop(withConstant: -2, priority: .required, relatedBy: .equal)
containerView.pinToSuperviewBottom(withConstant: -2, priority: .required, relatedBy: .equal)
quantityLabel.pinToSuperview(edges: [.top, .left, .bottom])
nameLabel.pinToSuperview(edges: [.top, .bottom])
nameLabel.pinLeft(to: quantityLabel, anchor: .right)
nameLabel.pinRight(to: priceLabel, anchor: .left)
priceLabel.pinToSuperview(edges: [.top, .right, .bottom])
}
}
Everything in the iCarousel view(cell) shows apart from the contents of the cell.
Any help or insight would be great