How can custom tableview cells be made in swift playground? - ios

In swift playground (not in a project)
Could you please give me a hint about how to do this please?enter image description here

You have to implement init(style:reuseIdentifier:) in your custom class to define subviews and constraints programmatically.
Thus:
import UIKit
import PlaygroundSupport
class CustomCell: UITableViewCell {
weak var label1: UILabel!
weak var label2: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let label1 = UILabel()
label1.translatesAutoresizingMaskIntoConstraints = false
label1.font = .preferredFont(forTextStyle: .title1)
label1.backgroundColor = #colorLiteral(red: 0.572549045085907, green: 0.0, blue: 0.23137255012989, alpha: 1.0)
label1.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
contentView.addSubview(label1)
self.label1 = label1
let label2 = UILabel()
label2.translatesAutoresizingMaskIntoConstraints = false
label2.font = .preferredFont(forTextStyle: .title1)
label2.backgroundColor = #colorLiteral(red: 0.0901960805058479, green: 0.0, blue: 0.301960796117783, alpha: 1.0)
label2.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
contentView.addSubview(label2)
self.label2 = label2
NSLayoutConstraint.activate([
label1.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
label2.leadingAnchor.constraint(equalTo: label1.trailingAnchor, constant: 10),
contentView.trailingAnchor.constraint(equalTo: label2.trailingAnchor, constant: 10),
label1.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
label2.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: label1.bottomAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: label2.bottomAnchor, constant: 10),
label1.widthAnchor.constraint(equalTo: label2.widthAnchor)
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And then you'd have a standard UITableViewController which registers this class and uses it:
class ViewController: UITableViewController {
var objects: [String] = {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
return Array(0 ..< 1000).flatMap { formatter.string(from: NSNumber(value: $0)) }
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
// set it up to let auto-layout resize the cell
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label1.text = objects[indexPath.row]
cell.label2.text = "Row \(indexPath.row)"
return cell
}
}
PlaygroundPage.current.liveView = ViewController()
Now that just creates a simple view like this:
That's not terribly pretty, but I set the background colors like I did, just so you can see that the frames are set appropriately. You'd obviously adjust your init(style:reuseIdentifier:) to create subviews more to your liking.
If you want the standard cell, then you don't need to have a custom cell subclass, but you can just use UITableViewCell:
import UIKit
import PlaygroundSupport
class ViewController: UITableViewController {
var objects: [String] = {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
return Array(0 ..< 1000).flatMap { formatter.string(from: NSNumber(value: $0)) }
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = objects[indexPath.row]
return cell
}
}
PlaygroundPage.current.liveView = ViewController()
On the iPad, that yields:
On Playground in Xcode, it yields:

Full credit to #Rob, this is a tweak of his answer, but after trying to directly add the cell to a table view to no avail (cells seem to ignore explicit sizing), I reduced his view controller to a generic one for ease of re-use.
class DashboardViewController<T: UITableViewCell>: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(T.self, forCellReuseIdentifier: "Cell")
// set it up to let auto-layout resize the cell
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! T
configure(cell)
return cell
}
var configure: ((T) -> ())!
static func with(configuration: ((T) -> ())!) {
let controller = DashboardViewController<T>()
controller.configure = configuration
PlaygroundPage.current.liveView = controller
}
}
// usage:
DashboardViewController<MyCustomCell>.with() { cell in
let model = MyCellModel()
cell.model = model }

Related

IOS 16 UITableViewCell accessibility API not working, breaking XCTest

Since the upgrade to IOS 16 the accessibility api for cells has stopped working.
Using this API's is not working anymore:
In a CellConfiguration
public struct CellConfiguration: UIContentConfiguration {
public func makeContentView() -> UIView & UIContentView {
CellContent(configuration: self)
}
public func updated(for state: UIConfigurationState) -> CellConfiguration {
self
}
}
private class CellContent: UIView, UIContentView {
init(configuration: UIContentConfiguration) {
self.configuration = configuration
super.init(frame: .zero)
isAccessibilityElement = true
accessibilityLabel = "I'm a label"
accessibilityValue = "I'm a value"
accessibilityTraits = [.header] // or other traits as well
accessibilityIdentifier = "cellID"
}
}
In a UITableViewController
private func createCell(in tableView: UITableView) -> UITableViewCell? {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier")
else { return nil }
cell.contentConfiguration = CellConfiguration()
cell.selectionStyle = .none
return cell
}
The cell is not accessible in the simulator and on the phone. I can't find it. This has worked prior to IOS16. Thus breaking any existing XCTest's and any progress implementing app that has accessibility.
This is the right way, declare objects and set attributed an constraints:
class YourViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let cellId = "cellId"
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
fileprivate func setupTableView() {
view.backgroundColor = .white
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(MyCell.self, forCellReuseIdentifier: cellId) // register your cell
tableView.backgroundColor = #colorLiteral(red: 0.94826442, green: 0.9495814443, blue: 0.96922189, alpha: 1)
tableView.separatorColor = UIColor(white: 1, alpha: 0.4)
//tableView.tableFooterView = UIView()
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0 // remove blank space on top o tableView
} else {
UITableView.appearance().sectionHeaderTopPadding = CGFloat(0) // remove blank space on top o tableView in old OS
}
now set delegate and datasource:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let v = UIView()
let label = UILabel()
switch section {
case 0:
v.backgroundColor = #colorLiteral(red: 0.94826442, green: 0.9495814443, blue: 0.96922189, alpha: 1)
label.text = "Section 1"
default:
label.text = "Section 2"
v.backgroundColor = #colorLiteral(red: 0.94826442, green: 0.9495814443, blue: 0.96922189, alpha: 1)
}
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .systemBlue
v.addSubview(label)
label.topAnchor.constraint(equalTo: v.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 20).isActive = true
label.trailingAnchor.constraint(equalTo: v.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: v.bottomAnchor).isActive = true
return v
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 1:
return 1
default:
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
let section = indexPath.section
switch section {
case 0:
cell.myLabel.text = "Title: I'm a label"
cell.subtitle.text = "Subtitle: I'm a value"
default:
cell.myLabel.text = "Title: I'm a label2"
cell.subtitle.text = "Subtitle: I'm a value2"
}
return cell
}
Your cell looks like:
class MyCell: UITableViewCell {
let myLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let subtitle: UILabel = {
let label = UILabel()
label.textColor = .gray
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .white
let stackView = UIStackView(arrangedSubviews: [myLabel, subtitle])
stackView.distribution = .fillEqually
stackView.axis = .vertical
stackView.spacing = 2
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
this is the result:

How to shift the UItable View down and anchor it to an image programatically

Hello what I have is a UITableViewController which displays various sliding out menu options. The idea I have is instead of the menu options to be displayed right below the navigation bar but rather include an image and have these items displayed below the image. I tried to anchor the UITable view to the bottom anchor of the image but it does not work.
Below are some images:
class MenuViewController: UITableViewController {
public var delegate: MenuControllerDelagate?
private let menuItems: [MenuOptions]
let darkColour = UIColor(displayP3Red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1)
private let profileImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.isUserInteractionEnabled = true
iv.image = #imageLiteral(resourceName: "venom-7")
return iv
}()
init(with menuItems: [MenuOptions]) {
self.menuItems = menuItems
super.init(nibName: nil, bundle: nil)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(profileImageView)
profileImageView.anchor(top: view.topAnchor)
profileImageView.setDimensions(height: 130, width: 130)
profileImageView.layer.cornerRadius = 130/2
profileImageView.centerX(inView: view)
tableView.backgroundColor = darkColour
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menuItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.textColor = .white
cell.textLabel?.text = menuItems[indexPath.row].rawValue
cell.backgroundColor = darkColour
cell.imageView?.image = UIImage(systemName: MenuOptions.allCases[indexPath.row].imageName)
cell.imageView?.tintColor = .white
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true )
let selectedItem = menuItems[indexPath.row]
delegate?.didSelectMenuItem(named: selectedItem)
}
}
Any advice how I could properly anchor the list items to be below the image would be greatly appreciated!
It seems like you're using UITableViewController and it doesn't explicitly add subviews as UIViewCroller. Your must conform ViewController to UIViewController.
After that initialize your UITableView
lazy var tableView: UITableView {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}
and in viewDidLoad() write
view.addSubview(tableView)
then create a function
func setupViews() {
NSLayoutConstraint.activate([
profileImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
profileImageView.heightAnchor.constraint(equalToConstant: 100),
profileImageView.widthAnchor.constraint(equalTo: profileImageView.heightAnchor, multiplier: 1.0),
profileImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
tableView.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 20),
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}
and call the setupViews() in viewDidLoad
From what I understand, instead of using UITableviewController you should use a UIViewController and add a UITableView in it then set the constraints.
class MenuViewController: UITableViewController
Instead use
class MenuViewController: UIViewController {
private lazy var tableView: UITableView = {
...
}
}
Set the constraints now and subview. I think that would solve the issue

Setting constraints to UITableView Footer Swft

I'm trying to place a footer with a UIButton, now I placed it only with frame and when I try it out across apple's iphones the button is not placed well due to the fact that I didn't set any auto-layout, the thing is Im trying to set auto-layout to the footer and it fails all the time also I'm not sure if its possible doing that, would love to get a handy help with that or even hints :).
Here's my code of the tableView:
import UIKit
import PanModal
protocol FilterTableViewDelegate {
func didUpdateSelectedDate(_ date: Date)
}
class FilterTableViewController: UITableViewController, PanModalPresentable {
var panScrollable: UIScrollView? {
return tableView
}
var albumsPickerIndexPath: IndexPath? // indexPath of the currently shown albums picker in tableview.
var delegate: FilterTableViewDelegate?
var datesCell = DatesCell()
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
// registerTableViewCells()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// tableView.frame = view.bounds
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
// MARK: - View Configurations
func setupTableView() {
tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
tableView.separatorStyle = .singleLine
tableView.isScrollEnabled = false
tableView.allowsSelection = true
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 600
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
}
func indexPathToInsertDatePicker(indexPath: IndexPath) -> IndexPath {
if let albumsPickerIndexPath = albumsPickerIndexPath, albumsPickerIndexPath.row < indexPath.row {
return indexPath
} else {
return IndexPath(row: indexPath.row + 1, section: indexPath.section)
}
}
// MARK: - UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// If datePicker is already present, we add one extra cell for that
if albumsPickerIndexPath != nil {
return 5 + 1
} else {
return 5
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let byActivityCell = UINib(nibName: "byActivityCell",bundle: nil)
self.tableView.register(byActivityCell,forCellReuseIdentifier: "byActivityCell")
let activityCell = tableView.dequeueReusableCell(withIdentifier: "byActivityCell", for: indexPath) as! byActivityCell
activityCell.selectionStyle = .none
return activityCell
case 1:
let byTypeCell = UINib(nibName: "ByType",bundle: nil)
self.tableView.register(byTypeCell,forCellReuseIdentifier: "byTypeCell")
let typeCell = tableView.dequeueReusableCell(withIdentifier: "byTypeCell", for: indexPath) as! ByType
typeCell.selectionStyle = .none
return typeCell
case 2:
let byHashtagsCell = UINib(nibName: "ByHashtags",bundle: nil)
self.tableView.register(byHashtagsCell,forCellReuseIdentifier: "byHashtagsCell")
let hashtagsCell = tableView.dequeueReusableCell(withIdentifier: "byHashtagsCell", for: indexPath) as! ByHashtags
hashtagsCell.selectionStyle = .none
return hashtagsCell
case 3:
let byDatesCell = UINib(nibName: "DatesCell",bundle: nil)
self.tableView.register(byDatesCell,forCellReuseIdentifier: "byDatesCell")
let datesCell = tableView.dequeueReusableCell(withIdentifier: "byDatesCell", for: indexPath) as! DatesCell
datesCell.selectionStyle = .none
datesCell.datesTableViewCellDelegate = self
return datesCell
case 4:
let byAlbumCell = UINib(nibName: "AlbumCell",bundle: nil)
self.tableView.register(byAlbumCell,forCellReuseIdentifier: "byAlbumCell")
let albumCell = tableView.dequeueReusableCell(withIdentifier: "byAlbumCell", for: indexPath) as! AlbumCell
albumCell.configureCell(choosenAlbum: "Any")
albumCell.selectionStyle = .none
return albumCell
case 5:
let albumPickerCell = UINib(nibName: "AlbumsPickerTableViewCell", bundle: nil)
self.tableView.register(albumPickerCell, forCellReuseIdentifier: "albumPickerCell")
let albumsPicker = tableView.dequeueReusableCell(withIdentifier: "albumPickerCell", for: indexPath) as! AlbumsPickerTableViewCell
return albumsPicker
default:
return UITableViewCell()
}
}
// MARK: - footer Methods:
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return getfooterView()
}
func getfooterView() -> UIView
{
let footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 400))
let applyFiltersBtn = UIButton(frame: CGRect(x: 0, y: 0, width: 380, height: 40))
applyFiltersBtn.center = footerView.center
applyFiltersBtn.layer.cornerRadius = 12
applyFiltersBtn.layer.masksToBounds = true
applyFiltersBtn.setTitle("Apply Filters", for: .normal)
applyFiltersBtn.backgroundColor = #colorLiteral(red: 0.1957295239, green: 0.6059523225, blue: 0.960457623, alpha: 1)
// doneButton.addTarget(self, action: #selector(hello(sender:)), for: .touchUpInside)
footerView.addSubview(applyFiltersBtn)
return footerView
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 10
}
// MARK: TableViewDelegate Methods:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
tableView.beginUpdates()
// 1 - We Delete the UIPicker when the user "Deselect" thr row.
if let datePickerIndexPath = albumsPickerIndexPath, datePickerIndexPath.row - 1 == indexPath.row {
tableView.deleteRows(at: [datePickerIndexPath], with: .fade)
self.albumsPickerIndexPath = nil
} else {
// 2
// if let datePickerIndexPath = albumsPickerIndexPath {
// tableView.deleteRows(at: [datePickerIndexPath], with: .fade)
// }
albumsPickerIndexPath = indexPathToInsertDatePicker(indexPath: indexPath)
tableView.insertRows(at: [albumsPickerIndexPath!], with: .fade)
tableView.deselectRow(at: indexPath, animated: true)
}
tableView.endUpdates()
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if indexPath.row == 4 {
return indexPath
} else {
return nil
}
}
}
extension FilterTableViewController: DatesTableViewCellDelegate {
func didButtonFromPressed() {
print("Button From is Pressed")
let pickerController = CalendarPickerViewController(
baseDate: Date(),
selectedDateChanged: { [weak self] date in
guard let self = self else { return }
// self.item.date = date
//TODO: Pass the date to the DatesCell to update the UIButtons.
self.delegate?.didUpdateSelectedDate(date)
self.tableView.reloadRows(at: [IndexPath(row: 3, section: 0)], with: .fade)
})
present(pickerController, animated: true, completion: nil)
}
func didButtonToPressed() {
//TODO: Present our custom calendar
let calendarController = CalendarPickerViewController(
baseDate: Date(),
selectedDateChanged: { [weak self] date in
guard let self = self else { return }
self.tableView.reloadRows(at: [IndexPath(row: 3, section: 0)], with: .fade)
})
self.present(calendarController, animated: true, completion: nil)
}
}
First, a tip (based on this and other questions you've posted): Simplify what you're doing. When you post code referencing 5 different cell classes along with code for handling cell action delegates, inserting and deleting, etc... but your question is about Section Footer layout, it gets difficult to help.
So, here's an example of a simple table view controller, using a default cell class, and a custom UITableViewHeaderFooterView for the section footer:
class MySectionFooterView: UITableViewHeaderFooterView {
let applyFiltersBtn = UIButton()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configureContents()
}
func configureContents() {
applyFiltersBtn.translatesAutoresizingMaskIntoConstraints = false
applyFiltersBtn.layer.cornerRadius = 12
applyFiltersBtn.layer.masksToBounds = true
applyFiltersBtn.setTitle("Apply Filters", for: .normal)
applyFiltersBtn.backgroundColor = #colorLiteral(red: 0.1957295239, green: 0.6059523225, blue: 0.960457623, alpha: 1)
contentView.addSubview(applyFiltersBtn)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
// use layoutMarginsGuide for top and bottom
applyFiltersBtn.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
applyFiltersBtn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
// inset button 20-pts from layoutMarginsGuide on each side
applyFiltersBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
applyFiltersBtn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// make button height 40-pts
applyFiltersBtn.heightAnchor.constraint(equalToConstant: 40.0),
])
}
}
class SectionFooterViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// register cell class for reuse
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
// register Section Footer class for reuse
tableView.register(MySectionFooterView.self, forHeaderFooterViewReuseIdentifier: "mySectionFooter")
tableView.sectionFooterHeight = UITableView.automaticDimension
tableView.estimatedSectionFooterHeight = 50
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath)"
return cell
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let view = tableView.dequeueReusableHeaderFooterView(withIdentifier:"mySectionFooter") as! MySectionFooterView
return view
}
}
To handle tapping the button in the section footer, you can either assign it an action in viewForFooterInSection, or build that action into the MySectionFooterView class and use a closure or protocol / delegate pattern.

Tableview cells disappear upon scrolling down an up except for the last cell from each section which turns grey color

I am creating a tableview programmatically which is not a problem,
but making a programmatic table view cell is being a headache, it is the first time I do this.
I don't know what I am doing wrong as much as I have tried, I cannot debug this.
Here is the code for the view controller
import Foundation
import UIKit
extension CountryPhoneCodeList {
class ViewController : UIViewController {
var viewModel: ViewModel!
var router: Router!
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.translatesAutoresizingMaskIntoConstraints = false
table.dataSource = self
table.delegate = self
table.register(CountryPhoneCodeListCell.self, forCellReuseIdentifier: "CountryPhoneCodeListCell")
table.layer.backgroundColor = UIColor.white.cgColor
table.estimatedRowHeight = 44
table.rowHeight = UITableView.automaticDimension
table.separatorStyle = .none
return table
}()
var updateTextFieldCode : ((String, String) -> ())? = nil
override func viewDidLoad() {
self.configureUI()
}
private func configureUI(){
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
}
extension CountryPhoneCodeList.ViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedViewModel = viewModel.cellViewModelAt(section: indexPath.section, row: indexPath.row)
updateTextFieldCode?(selectedViewModel.flag, selectedViewModel.countryPhoneCode)
router.dismiss()
}
}
extension CountryPhoneCodeList.ViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.countryCodeListCellViewModels.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let character = viewModel.getAlphabetCharacterFromIndex(index: section)
guard let countryCodesStartingWithCharacter = viewModel.countryCodeListCellViewModels[character] else {fatalError("Could not get codes starting with letter \(character)")}
return countryCodesStartingWithCharacter.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect.zero)
let label = UILabel(frame: CGRect(x: 14, y: -8, width: 50, height: 50))
label.text = viewModel.getAlphabetCharacterFromIndex(index: section)
label.customise(for: .bodyBold)
view.addSubview(label)
view.backgroundColor = .white
return view
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "CountryPhoneCodeListCell", for: indexPath) as? CountryPhoneCodeListCell {
cell.viewModel = viewModel.cellViewModelAt(section: indexPath.section, row: indexPath.row)
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
}
class CountryPhoneCodeListViewController : CountryPhoneCodeList.ViewController {}
And this is the code for the tableview cell
import UIKit
struct CountryPhoneCodeListCellViewModel {
var countryName = ""
var countryPhoneCode = ""
var flag = ""
}
class CountryPhoneCodeListCell: UITableViewCell {
private var countryNameLabel: UILabel = .buildBodyLabel()
private var countryPhoneCodeLabel: UILabel = .buildBodyLabel()
var viewModel: CountryPhoneCodeListCellViewModel? {
didSet {
guard let viewModel = viewModel else {fatalError("Cannot unwrap viewModel")}
countryNameLabel.text = viewModel.countryName
countryNameLabel.customise(for: .body)
countryPhoneCodeLabel.text = viewModel.countryPhoneCode
countryPhoneCodeLabel.customise(for: .body)
}
}
override func prepareForReuse() {
countryNameLabel.text = nil
countryPhoneCodeLabel.text = nil
}
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(countryNameLabel)
contentView.addSubview(countryPhoneCodeLabel)
NSLayoutConstraint.activate([
countryNameLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: .constant16),
countryNameLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: .constant16),
countryNameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .constant4),
countryPhoneCodeLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .constant16),
countryPhoneCodeLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: .constant16),
countryPhoneCodeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .constant16)
])
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}

Swift UITableView separator hidden until scroll

I have implemented a custom table view cell which appears but without the separator until you scroll. In the viewDidAppear I have set the separator style, the label border is not overlapping the cell edges. Help
Before Scrolling
After Scrolling
Larger Sim Window
On device testing
The pattern is MVVM with a custom cell.
Model
import Foundation
struct OAuthList {
let providers: [String]
init() {
self.providers = OAuthProviders.providers
}
}
View Model
import Foundation
struct OAuthListViewModel {
var providerList: [String]
init(providers: [String]) {
self.providerList = providers
}
}
LoginViewController
import UIKit
class LoginViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
var providerButtons = OAuthListViewModel(providers: OAuthProviders.providers)
override func viewDidLoad() {
super.viewDidLoad()
let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 17)]
self.navigationController?.navigationBar.titleTextAttributes = attributes
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.barTintColor = #colorLiteral(red: 1, green: 0.738589704, blue: 0.9438112974, alpha: 1)
self.navigationItem.title = "LOGIN / SIGNUP"
self.navigationItem.leftBarButtonItem?.tintColor = .white
self.navigationItem.leftBarButtonItem?.isEnabled = false
self.tableView.separatorColor = .white
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(CustomCell.self, forCellReuseIdentifier: TextCellIdentifier.textCellIdentifier)
self.tableView.layoutMargins = UIEdgeInsets.zero
self.tableView.separatorInset = UIEdgeInsets.zero
self.tableView.tableFooterView = UIView()
}
}
extension LoginViewController {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return providerButtons.providerList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TextCellIdentifier.textCellIdentifier, for: indexPath) as! CustomCell
let row = indexPath.row
cell.backgroundColor = #colorLiteral(red: 1, green: 0.738589704, blue: 0.9438112974, alpha: 1)
cell.buttonLabel.text = providerButtons.providerList[row]
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(providerButtons.providerList[indexPath.row])
}
}
Custom Cell
class CustomCell: UITableViewCell {
var labelText: String?
var buttonLabel: UILabel = {
var label = UILabel()
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: TextCellIdentifier.textCellIdentifier)
self.addSubview(buttonLabel)
buttonLabel.translatesAutoresizingMaskIntoConstraints = false
buttonLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
buttonLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
buttonLabel.textColor = UIColor.white
}
override func layoutSubviews() {
if let labelText = labelText {
buttonLabel.text = labelText
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can hide the separator and simply add UIView to act as a separator inside your custom cells
Enlarge your simulator window or try it on an actual device not sim.
When the sim window is small enough it can have a hard time displaying separators in a tableview since the separators are normally only 0.5pt in height.
A good indicator that the issue I mentioned above is occurring is when you scroll the tableview on the simulator and random separators start to appear while some are hidden. Which is what appears to be happening in your second screenshot.

Resources