I created a live playground below to illustrate the question. When using self-sizing table cells, the insert row animation (bottom-up) is executed differently depending on the size of the last cell. If the last cell is taller than the cell that is about to be inserted, the animation begins too high and thus animates downward (not upward). What is going on here?
import UIKit
import PlaygroundSupport
class Live: UIViewController {
private var entries = ["a\na\na", "b\nb\nb", "c\nc\nc", "d\nd\nd", "e\ne\ne", "f"]
private let newEntries = ["correct\nanimation\nhere", "correct\nanimation\nhere", "incorrect animation", "correct\nanimation\nhere", "incorrect animation", "correct\nanimation\nhere"]
private let tableView = UITableView()
override func loadView() {
view = UIView()
view.backgroundColor = .blue
tableView.dataSource = self
tableView.delegate = self
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 64, right: 0)
tableView.backgroundColor = .purple
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(EntryCell.self, forCellReuseIdentifier: EntryCell.reuseId)
view.addSubview(tableView)
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
for (n, new) in newEntries.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(n*2)) {
self.entries.append(new)
self.tableView.performBatchUpdates({
self.tableView.insertRows(at: [IndexPath(row: self.entries.count - 1, section: 0)], with: .bottom)
}) { (finished) in
if finished {
DispatchQueue.main.async {
self.tableView.scrollToRow(at: IndexPath(row: self.entries.count - 1, section: 0), at: .bottom, animated: true)
}
}
}
}
}
}
}
extension Live: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return entries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: EntryCell.reuseId, for: indexPath) as! EntryCell
cell.entryLabel.text = entries[indexPath.row]
return cell
}
}
extension Live: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return UIView()
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0
}
}
extension Live {
private class EntryCell: UITableViewCell {
static let reuseId = "cell"
let entryLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
isOpaque = true
selectionStyle = .none
backgroundColor = .brown
entryLabel.textColor = .white
entryLabel.numberOfLines = 0
entryLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(entryLabel)
entryLabel.topAnchor.constraint(equalTo: topAnchor, constant: 16).isActive = true
entryLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
entryLabel.widthAnchor.constraint(equalTo: widthAnchor, constant: -32).isActive = true
entryLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
}
PlaygroundPage.current.liveView = Live()
Just changed insert rows from bottom to automatic, it will load cells as per scroll.
self.tableView.insertRows(at: [IndexPath(row: self.entries.count - 1, section: 0)], with: .automatic)
Related
I have been looking around for a solution or a best way to determine the height of a tableView row in heightForRowAt, that has a tableView based on some conditions in the data model.
When my data model has a data type called MULTISELECT, I need to display a cell with a tableView inside it. There are no problems in doing so. The inner tableView's data is assigned in outer tableView's cellForRowAt.
The question here is how to get the height of my outer tableView row for the MULTISELECT type cells, after the data is populated for the inner tableView rows?
Outer tableView code (inside a ViewController) -
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let preferenceCategories = self.preferenceCategories else {
return UITableViewCell()
}
let categoryCode = preferenceCategories[indexPath.section].code
let filteredPreferenceSet = self.preferenceSet.filter({$0.categoryCode == categoryCode}).filter({$0.dataType == "BOOLEAN"/* || $0.dataType == "MULTISELECT"*/})
if let preferenceDataType = filteredPreferenceSet[indexPath.row].dataType {
if preferenceDataType == "BOOLEAN" {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "CustPrefSetCell", for: indexPath) as! CustPrefSetCell
cell.preferenceName.text = filteredPreferenceSet[indexPath.row].name
cell.preferenceDescription.text = filteredPreferenceSet[indexPath.row].description
cell.switchDelegate = self
let propertyValue = ((filteredPreferenceSet[indexPath.row].value ?? "false") as NSString).boolValue
propertyValue ? cell.preferenceSwitch.setOn(true, animated: true) : cell.preferenceSwitch.setOn(false, animated: true)
cell.preferenceCode = filteredPreferenceSet[indexPath.row].code
return cell
}
else if preferenceDataType == "MULTISELECT" {
let multiSelectCell = self.tableView.dequeueReusableCell(withIdentifier: "CustPrefMultiSelectTableViewCell", for: indexPath) as! CustPrefMultiSelectTableViewCell
multiSelectCell.preferenceValues = filteredPreferenceSet[indexPath.row].preferenceValues
// self.rowHeight = multiSelectCell.tableView.contentSize.height
return multiSelectCell
}
else {
return UITableViewCell()
}
}
else {
return UITableViewCell()
}
}
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
The inner tableView is inside the multiSelectCell, whose code is below -
class CustPrefMultiSelectTableViewCell: UITableViewCell {
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var preferenceDescription: UILabel!
#IBOutlet weak var preferenceTitle: UILabel!
#IBOutlet weak var tableView: UITableView!
var preferenceValues: [PreferenceValue]?
override func awakeFromNib() {
super.awakeFromNib()
self.tableView.delegate = self
self.tableView.dataSource = self
guard let frameworkBundle = Bundle(identifier: "com.frameworkbundle.asdf") else {
fatalError("Framework bundle identifier is incorrect.")
}
let custPrefHeaderCell = UINib(nibName: "CustPrefMultiSelectPreferenceTableViewCell", bundle: frameworkBundle)
self.tableView.register(custPrefHeaderCell, forCellReuseIdentifier: "CustPrefMultiSelectPreferenceTableViewCell")
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 64.0
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension CustPrefMultiSelectTableViewCell: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let preferenceValues = self.preferenceValues else {
return 0
}
return preferenceValues.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let preferenceCategories = self.preferenceValues else {
return UITableViewCell()
}
let cell = self.tableView.dequeueReusableCell(withIdentifier: "CustPrefMultiSelectPreferenceTableViewCell", for: indexPath) as! CustPrefMultiSelectPreferenceTableViewCell
cell.preferenceName.text = preferenceCategories[indexPath.row].name
cell.preferenceDescription.text = preferenceCategories[indexPath.row].description
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
I thought of an approach by having a height constraint for the inner tableView, and update the outer tableView height when it is ready/reloaded with data. But where should I implement that logic? With a fixed height of inner tableView, I get an unwanted behavior of scrolling. That need to be avoided.
How do I go further with this?
Thanks in advance!
I think using nested tableView is not the best solution, anyway, I hope this example will help you.
struct Foo {
let strings: [String]
}
class NestedViewController: UIViewController {
let dataSource = [Foo(strings: ["String1", "String2"]),
Foo(strings: ["Long long long long long long long long long long long long long string"])]
let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(NestedCell.self, forCellReuseIdentifier: NestedCell.identifier)
tableView.tableFooterView = UIView()
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
setupConstraints()
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData()
}
func setupConstraints() {
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
extension NestedViewController: UITableViewDelegate & UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: NestedCell.identifier, for: indexPath) as? NestedCell else {
return UITableViewCell()
}
cell.setup(foo: dataSource[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
NestedCell.heightFor(foo: dataSource[indexPath.row])
}
}
class NestedCell: UITableViewCell {
static let identifier = "NestedCell"
let nestedTableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(TextCell.self, forCellReuseIdentifier: TextCell.identifier)
tableView.tableFooterView = UIView()
return tableView
}()
private var foo = Foo(strings: [""])
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(nestedTableView)
setConstraints()
nestedTableView.dataSource = self
nestedTableView.delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup(foo: Foo) {
self.foo = foo
nestedTableView.reloadData()
}
static func heightFor(foo: Foo) -> CGFloat {
foo.strings.reduce(0) { $0 + TextCell.heightFor(text: $1) }
}
private func setConstraints() {
NSLayoutConstraint.activate([
nestedTableView.topAnchor.constraint(equalTo: contentView.topAnchor),
nestedTableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
nestedTableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
nestedTableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
}
}
extension NestedCell: UITableViewDelegate & UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
foo.strings.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TextCell.identifier, for: indexPath) as? TextCell else {
return UITableViewCell()
}
cell.setup(text: foo.strings[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
TextCell.heightFor(text: foo.strings[indexPath.row])
}
}
class TextCell: UITableViewCell {
static let identifier = "TextCell"
static let labelOffset: CGFloat = 10
private let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.font = .systemFont(ofSize: 15, weight: .medium)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(label)
setConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup(text: String) {
label.text = text
}
static func heightFor(text: String) -> CGFloat {
text.height(width: UIScreen.main.bounds.width - 2 * TextCell.labelOffset,
font: .systemFont(ofSize: 15, weight: .medium)) + 2 * TextCell.labelOffset
}
private func setConstraints() {
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: TextCell.labelOffset),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -TextCell.labelOffset),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: TextCell.labelOffset),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -TextCell.labelOffset)
])
}
}
extension String {
func height(width: CGFloat, font: UIFont) -> CGFloat {
let rect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.height)
}
}
I have UIViewTable created programmatically
I customised the headers and cell look via Extension.
All I need is to make the large amount of texts displayed in header/cell to be viewed with:
lineBreakMode = NSLineBreakMode.byWordWrapping // enable multi line
numberOfLines = 0 // for Automatic size
I nearly used everything, but nothing is working.
I used:
self.tableView.estimatedRowHeight = 200.0
self.tableView.rowHeight = UITableView.automaticDimension
I put:
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
I also did:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
Nothing seems to work
here is my Extension:
extension indexController {
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let subDatas = sections[section].sub_catigories // [1]
return subDatas?.count ?? 0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let currentSection = sections[indexPath.section]
let currentSubdata = currentSection.sub_catigories?[indexPath.row]
//print(currentSubdata!.id)
let vc = indexControllerTwo()
vc.catNumber = currentSubdata!.id
vc.sectionTitle = currentSubdata?.name
navigationController?.pushViewController(vc, animated: true)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellid", for: indexPath)
// [2]
let currentSection = sections[indexPath.section]
let currentSubdata = currentSection.sub_catigories![indexPath.row]
// use listCell
guard let titleCell = cell as? listCell else {
return cell
}
titleCell.titleLabel.text = currentSubdata.name
titleCell.listCount.text = "\(currentSubdata.number_of_subcatigories ?? 0)"
// titleCell.titleLabel.numberOfLines = 3
// titleCell.titleLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
// titleCell.titleLabel.baselineAdjustment = .alignCenters
// titleCell.titleLabel.adjustsFontSizeToFitWidth = true
// self.tableView.estimatedRowHeight = 200.0
// self.tableView.rowHeight = UITableView.automaticDimension
cell.layer.backgroundColor = UIColor.clear.cgColor
return cell
}
Please note that: listCell is just for customization and constraint
and here it is:
import UIKit
class listCell: UITableViewCell {
var safeArea: UILayoutGuide!
let imageCell = UIImageView()
let titleLabel = UILabel()
let subTitleLabel = UILabel()
let listCount = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView(){
safeArea = layoutMarginsGuide
setupTitleLabel()
setupListCount()
}
func setupTitleLabel(){
addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 7)
])
titleLabel.font = UIFont(name: "verdana-Bold", size: 16)
}
func setupListCount(){
addSubview(listCount)
listCount.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
listCount.leadingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -30),
listCount.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: -11)
])
listCount.font = UIFont(name: "verdana", size: 10)
}
}
Please help me make the header and cell text field to be auto resizing.
thanks for your time.
I am looking to change the background colour and text colour of the uitableview sections using viewForHeaderInSection like so:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionHeader = UIView()
let sectionText = UILabel()
sectionHeader.backgroundColor = .blue
sectionText.textColor = .red
sectionText.font = .systemFont(ofSize: 14, weight: .bold)
sectionText.text = painkillersArray[section]["label"] as? String
sectionHeader.addSubview(sectionText)
return sectionHeader
}
The background is working but the text is not appearing. What am I doing wrong?
you need to give frame to both the view and label and also you have to provide heightForHeaderInSection:-
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionHeader = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tableView.frame.width, height: 50))
let sectionText = UILabel()
sectionText.frame = CGRect.init(x: 5, y: 5, width: sectionHeader.frame.width-10, height: sectionHeader.frame.height-10)
sectionText.text = "Custom Text"
sectionText.font = .systemFont(ofSize: 14, weight: .bold) // my custom font
sectionText.textColor = .red // my custom colour
sectionHeader.addSubview(sectionText)
return sectionHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60 // my custom height
}
===
EDIT: I removed the first code I wrote, which didn't handle autolayout correctly. Morevoer, as #rmaddy pointed out in the comment below, it would be better if you use the provided UITableViewHeaderFooterView as is.
Register
tableView.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "header")
in viewDidLoad and do
override func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header")
header?.textLabel?.text = "foo"
return header
}
header?.textLabel?.textColor = .red won't work in the above method, so put customization code in
override func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header")
header?.textLabel?.text = "foo"
return header
}
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
if let header = view as? UITableViewHeaderFooterView {
header.textLabel?.textColor = .red
header.backgroundView?.backgroundColor = .blue
}
}
===
To use autolayout constraints with more complex headers, provide a custom UITableViewHeaderFooterView subclass:
class CustomTableViewHeaderView: UITableViewHeaderFooterView {
func commonInit() {
let sectionHeader = UIView()
let sectionText = UILabel()
sectionHeader.backgroundColor = .blue
sectionText.textColor = .red
sectionText.font = .systemFont(ofSize: 14, weight: .bold)
sectionText.text = "foo"
sectionHeader.translatesAutoresizingMaskIntoConstraints = false
sectionText.translatesAutoresizingMaskIntoConstraints = false
sectionHeader.addSubview(sectionText)
addSubview(sectionHeader)
NSLayoutConstraint.activate([
sectionHeader.leadingAnchor.constraint(equalTo: leadingAnchor),
sectionHeader.trailingAnchor.constraint(equalTo: trailingAnchor),
sectionHeader.topAnchor.constraint(equalTo: topAnchor),
sectionHeader.bottomAnchor.constraint(equalTo: bottomAnchor),
sectionText.leadingAnchor.constraint(equalTo: sectionHeader.leadingAnchor, constant: 16),
sectionText.trailingAnchor.constraint(equalTo: sectionHeader.trailingAnchor),
sectionText.topAnchor.constraint(equalTo: sectionHeader.topAnchor, constant: 8),
sectionText.bottomAnchor.constraint(equalTo: sectionHeader.bottomAnchor, constant: -8),
])
}
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
}
and register it in viewDidLoad():
func viewDidLoad() {
super.viewDidLoad()
...
tableView.register(CustomTableViewHeaderView.self, forHeaderFooterViewReuseIdentifier: "header")
...
}
Then simply dequeReusableHeaderFooterView:
func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
return tableView.dequeueReusableHeaderFooterView(withIdentifier: "header")
}
Implement heightForHeaderInSection and estimatedHeightForHeaderInSection similarly.
I'm trying to create a custom background for my tableview section. The look I'm going for is this:
How do I customize the tableview section background/layer please, rather than just the individual cells?
Edit: Just to clarify - I'm talking about the white background under 'Latest Transactions'. So the top of the section is rounded (as is bottom) but all the rows are rectangular.
Create UITableViewCell subclass and add a UIView with white color. Add left & right padding for the view to the cell. Add UILabel and other UI elements to this newly added view instead of adding in cell or its contentView. Set cell background color as UIColor.groupTableViewBackground
class CustomCell: UITableViewCell {
let bgView = UIView()
let label = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
backgroundColor = .groupTableViewBackground
bgView.backgroundColor = .white
bgView.translatesAutoresizingMaskIntoConstraints = false
addSubview(bgView)
label.translatesAutoresizingMaskIntoConstraints = false
bgView.addSubview(label)
bgView.topAnchor.constraint(equalTo: topAnchor).isActive = true
bgView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
bgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
bgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
label.heightAnchor.constraint(equalToConstant: 30).isActive = true
label.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 5).isActive = true
label.bottomAnchor.constraint(equalTo: bgView.bottomAnchor, constant: -5).isActive = true
label.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 5).isActive = true
label.trailingAnchor.constraint(equalTo: bgView.trailingAnchor, constant: -5).isActive = true
}
}
Use this cell class in your tableView. And set your view controller background color and tableView background color as UIColor.groupTableViewBackground
In cellForRowAt check if the cell is first or last cell of the section. If it is the first cell of the section, apply corner radius to top left, top right corners. If the cell is the last cell of the section, apply corner radius to bottom left, bottom right corners. If the cell is in the middle remove corner radius.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = .groupTableViewBackground
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
tableView.separatorInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 10)
tableView.tableFooterView = UIView()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section \(section) Title"
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell
?? CustomCell(style: .default, reuseIdentifier: "CustomCell")
if indexPath.row == 0 {//first cell of this section
cell.bgView.layer.cornerRadius = 15.0
cell.bgView.layer.masksToBounds = true
cell.bgView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else if indexPath.row == tableView.numberOfRows(inSection: indexPath.section)-1 {//last cell of this section
cell.bgView.layer.cornerRadius = 15.0
cell.bgView.layer.masksToBounds = true
cell.bgView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
} else {
cell.bgView.layer.cornerRadius = 0
}
cell.label.text = "Row \(indexPath.row)"
return cell
}
}
Have a look at func headerView(forSection section: Int) -> UITableViewHeaderFooterView?
headerView(forSection:) - UITableView | Apple Developer
Use UITableViewDelegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
<#code#>
}
This will help you to set the view of each section header programatically.
To create a section header in tableView, use UITableViewDelegate's tableView(_:viewForHeaderInSection:) and tableView(_:heightForHeaderInSection:) methods.
func getHeaderText(for section: Int) -> String? {
switch section {
case 0:
return "Latest Transactions"
default:
return "Other Transactions"
}
//Add other cases for different sections
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: UIScreen.main.bounds.width, height: 70.0)))
headerView.backgroundColor = .clear
let label = UILabel(frame: CGRect(x: 20, y: 0, width: headerView.bounds.width - 40, height: headerView.bounds.height))
label.text = self.getHeaderText(for: section)
label.font = UIFont.systemFont(ofSize: 16.0, weight: .semibold)
headerView.addSubview(label)
return headerView
//Customize the headerView as per your requirement
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 70.0
}
In the above code, you can configure the header label in getHeaderText(for:) for each section.
Also, I've created the headerView programatically. You can use a .xib for creating a custom headerView as well. That's completely your choice.
You can customize the headerView as per your requirements either programatically or in the interface.
I need to create a table with 50 rectangular cells and:
All of the cells must have the same properties but different contents;
Each cell has to be clickable;
Cells must have different items inside of them (images, labels etc).
I started programming recently and I'm 16 so please don't bite my head off if I wasn't clear or whatever...
By the way I tried to create a table view with custom cells but I'm not sure it's working, plus I'm in landscape mode so maybe I need to create them programmatically... Can you just please tell me if I'm doing it right using the custom cells and maybe give me some advice or send me some tutorials for these things I need to do?
Don't worry. Go ahead in your achieve. Here are some examples for you.
Your ViewController
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableReuseIdentifier = "TableCell"
let tableView: UITableView! = {
let table = UITableView(frame: .zero, style: UITableViewStyle.grouped)
table.backgroundColor = .clear
table.translatesAutoresizingMaskIntoConstraints = false
table.isUserInteractionEnabled = true
table.isMultipleTouchEnabled = true
return table
}()
var items: [U]? = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
//registering your customCell into tableView
self.tableView!.register(MyCustomCell.self, forCellReuseIdentifier: tableReuseIdentifier)
}
public func setupViews() {
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items?.count ?? 0
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: MyCustomCell = tableView.dequeueReusableCell(withIdentifier: tableReuseIdentifier, for: indexPath) as! MyCustomCell
//here you can manipulate some data to insert on cell
return cell
}
public func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("When cell has been selected")
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return true
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
}
Your Custom Cell:
class MyCustomCell: UITableViewCell {
// attribute example
let lblCompanyName: UILabel = {
let label = UILabel()
label.textColor = .lightGray
label.font = UIFont.systemFont(ofSize: 17, weight: .bold)
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.6
label.numberOfLines = 2
label.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: UILayoutConstraintAxis.vertical)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
// attribute example
let lblFrequencies: UILabel = {
let label = UILabel()
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.setContentHuggingPriority(UILayoutPriority.defaultLow, for: UILayoutConstraintAxis.vertical)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
// attribute like imageView example
let imageLogo: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.contentMode = .scaleAspectFit
image.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: UILayoutConstraintAxis.vertical)
image.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: UILayoutConstraintAxis.horizontal)
return image
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
[imageLogo, lblCompanyName, lblFrequencies].forEach { (view) in contentView.addSubview(view) }
let top: CGFloat = 20, bottom: CGFloat = -20, leading: CGFloat = 20, trailing: CGFloat = -20
self.imageLogo.topAnchor.constraint(equalTo: contentView.topAnchor, constant: top).isActive = true
self.imageLogo.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: bottom).isActive = true
self.imageLogo.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: leading).isActive = true
self.imageLogo.widthAnchor.constraint(equalTo: imageLogo.heightAnchor).isActive = true
self.lblCompanyName.topAnchor.constraint(equalTo: contentView.topAnchor, constant: top).isActive = true
self.lblCompanyName.leadingAnchor.constraint(equalTo: self.imageLogo.trailingAnchor, constant: 20).isActive = true
self.lblCompanyName.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: trailing).isActive = true
self.lblFrequencies.topAnchor.constraint(equalTo: self.lblCompanyName.bottomAnchor, constant: 5).isActive = true
self.lblFrequencies.leadingAnchor.constraint(equalTo: self.imageLogo.trailingAnchor, constant: 20).isActive = true
self.lblFrequencies.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: trailing).isActive = true
}
}
Hope it helped you. Cheers.