How can I make section header like Android sticky view? - ios

I am using the section header of the tableview.
https://www.youtube.com/watch?v=U3gqR4dRiT8
If you look at it, almost at the end
When the section header goes up, the existing top section header goes up and collides and disappears.
And the text in the top section header is changed and floats
But when I run the sample, the text changes.
It doesn't seem to have the effect of pushing up the section header.
What should I do?
import UIKit
class MyTableviewControllerTableViewController: UITableViewController {
private let numberOfSections = 3
var meat = ["Beef","Turkey","Fish", "Lamb", "Chicken", "Pork", "Beef","Turkey","Fish", "Lamb", "Chicken", "Pork"]
var fruit = ["Apple","Banana","Cherry","Apple","Banana","Cherry","Apple","Banana","Cherry"]
var vegetable = ["Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower"]
override func viewDidLoad() {
super.viewDidLoad()
// make the header sticky
self.automaticallyAdjustsScrollViewInsets = false
}
// MARK: - Table view data source
// return the number of sections
override func numberOfSections(in tableView: UITableView) -> Int {
return numberOfSections
}
// return the number of rows in the specified section
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rowCount = 0
switch (section) {
case 0:
rowCount = meat.count
case 1:
rowCount = fruit.count
case 2:
rowCount = vegetable.count
default:
rowCount = 0
}
return rowCount
}
// Content Cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
switch (indexPath.section) {
case 0:
cell.textLabel?.text = meat[indexPath.row]
case 1:
cell.textLabel?.text = fruit[indexPath.row]
case 2:
cell.textLabel?.text = vegetable[indexPath.row]
default:
cell.textLabel?.text = "Other"
}
return cell
}
// Header Cell
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell") as! CustomTableViewHeaderCell
headerCell.backgroundColor = UIColor.gray
switch (section) {
case 0:
headerCell.headerLabel.text = "Meat";
case 1:
headerCell.headerLabel.text = "Fruit";
case 2:
headerCell.headerLabel.text = "Vegetable";
default:
headerCell.headerLabel.text = "Other";
}
return headerCell
}
// Footer Cell
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: tableView.frame.size.width, height: 20))
let footerView = UIView(frame: rect)
footerView.backgroundColor = UIColor.darkGray
return footerView
}
// footer height
override func tableView(_ : UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 20.0
}

You can do it programmatically, and the result is perfect... Declare your table view and carry under your UIViewController (Attention, it's No UITableViewController) class:
class YourControllerClass: UIViewController {
let tableView = UITableView()
let cellId = "cellId"
let headerId = "headerId"
var meat = ["Beef","Turkey","Fish", "Lamb", "Chicken", "Pork", "Beef","Turkey","Fish", "Lamb", "Chicken", "Pork"]
var fruit = ["Apple","Banana","Cherry","Apple","Banana","Cherry","Apple","Banana","Cherry"]
var vegetable = ["Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower"]
now in viewDidLoad configure nav bar and set tableview attributes and constraints:
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar(largeTitleColor: .yourPreferColor, backgoundColor: .ultraDark, tintColor: .fuxiaRed, title: "My Title", preferredLargeTitle: true) // I use my function to configure navBar
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.register(HeaderCell.self, forCellReuseIdentifier: headerId)
tableView.backgroundColor = .yourPreferColor
tableView.delegate = self
tableView.dataSource = self
tableView.separatorColor = UIColor(white: 1, alpha: 0.2)
tableView.tableFooterView = UIView()
tableView.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
tableView.contentInsetAdjustmentBehavior = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0
tableView.sectionFooterHeight = 0
} else {
UITableView.appearance().sectionHeaderTopPadding = CGFloat(0)
UITableView.appearance().sectionFooterHeight = CGFloat(0)
}
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
}
Now create an extension for tableView delegate and datasource:
extension NavBarAppearenceController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50 // header height
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
let headerCell = tableView.dequeueReusableCell(withIdentifier: headerId) as! HeaderCell // header cell
headerCell.translatesAutoresizingMaskIntoConstraints = false
headerView.addSubview(headerCell)
headerCell.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true
headerCell.leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
headerCell.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
headerCell.bottomAnchor.constraint(equalTo: headerView.bottomAnchor).isActive = true
switch section {
case 0:
headerCell.label.text = "Meat"
case 1:
headerCell.label.text = "Fruit"
default:
headerCell.label.text = "Vegetables"
}
return headerView
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return meat.count
case 1:
return fruit.count
case 2:
return vegetable.count
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let mySection = indexPath.section
switch mySection {
case 0:
cell.textLabel?.text = "\(meat[indexPath.row])"
case 1:
cell.textLabel?.text = "\(fruit[indexPath.row])"
default:
cell.textLabel?.text = "\(vegetable[indexPath.row])"
}
cell.contentView.backgroundColor = .yourPreferColor
cell.textLabel?.textColor = .white
cell.selectionStyle = .none
return cell
}
}
Create your custom header cell:
class HeaderCell: UITableViewCell {
let myView: UIView = {
let v = UIView()
v.backgroundColor = .yourPreferColor
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let label = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .yourPreferColor
label.textColor = .white
label.font = .systemFont(ofSize: 20, weight: .bold)
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(myView)
myView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
myView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
myView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
myView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
myView.addSubview(label)
label.topAnchor.constraint(equalTo: myView.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: myView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: myView.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: myView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the result:
BONUS
This is my navBar configuration func:
func configureNavigationBar(largeTitleColor: UIColor, backgoundColor: UIColor, tintColor: UIColor, title: String, preferredLargeTitle: Bool) {
if #available(iOS 13.0, *) {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: largeTitleColor]
navBarAppearance.titleTextAttributes = [.foregroundColor: largeTitleColor]
navBarAppearance.backgroundColor = backgoundColor
navigationController?.navigationBar.standardAppearance = navBarAppearance
navigationController?.navigationBar.compactAppearance = navBarAppearance
navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
navigationController?.navigationBar.prefersLargeTitles = preferredLargeTitle
navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.tintColor = tintColor
navigationItem.title = title
} else {
// Fallback on earlier versions
navigationController?.navigationBar.barTintColor = backgoundColor
navigationController?.navigationBar.tintColor = tintColor
navigationController?.navigationBar.isTranslucent = false
navigationItem.title = title
}
}

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:

black area above navigation bar appears when search bar is not active?

I have a search controller added to navigationController.searchController. When the screen initially loads there is a black space above the navigation bar. Only when I tap on the search bar this space correctly shows white. When I tap off the search bar, this area shows black again.
import UIKit
import Foundation
import InstantSearch
class AlgoliaViewController: UIViewController, UISearchBarDelegate {
let userHitsInteractor: HitsInteractor<User> = .init(infiniteScrolling: .off, showItemsOnEmptyQuery: false)
let businessHitsInteractor: HitsInteractor<Business> = .init(infiniteScrolling: .off, showItemsOnEmptyQuery: false)
let multiSearch = MultiIndexSearcher(appID: APP_ID, apiKey: APP_KEY, indexNames: ["dev_USERS","dev_BUSINESS"])
lazy var multiConnector: MultiIndexSearchConnector = .init(searcher: multiSearch, indexModules: [
.init(indexName: "dev_USERS",
hitsInteractor: userHitsInteractor),
.init(indexName: "dev_BUSINESS",
hitsInteractor: businessHitsInteractor)
], searchController: searchController, hitsController: hitsTableViewController)
lazy var searchController: UISearchController = .init(searchResultsController: hitsTableViewController)
let hitsTableViewController = MultiIndexController()
let statsInteractor: StatsInteractor = .init()
override func viewDidLoad() {
super.viewDidLoad()
multiConnector.connect()
multiSearch.search()
statsInteractor.connectController(self)
setupUI()
}
func setupUI() {
view.backgroundColor = .white
navigationItem.searchController = searchController
navigationController?.navigationBar.backgroundColor = .white
// navigationItem.titleView = searchController.searchBar
navigationController?.navigationBar.backgroundColor = .white
navigationController?.navigationBar.isTranslucent = false
navigationController?.hidesBarsOnSwipe = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.showsSearchResultsController = true
searchController.automaticallyShowsCancelButton = false
searchController.searchBar.delegate = self
searchController.obscuresBackgroundDuringPresentation = false
hitsTableViewController.view.backgroundColor = .white
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchController.isActive = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchController.isActive = false
}
}
extension AlgoliaViewController: StatsTextController {
func setItem(_ item: String?) {
title = item
}
}
class MultiIndexController: UITableViewController, MultiIndexHitsController {
var hitsSource: MultiIndexHitsSource?
let cellID = "cellID"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(ResultsItemCell.self, forCellReuseIdentifier: cellID)
setupUI()
self.tableView.keyboardDismissMode = .onDrag
}
func setupUI() {
view.backgroundColor = .white
tableView.backgroundColor = .white
tableView.separatorStyle = .none
}
override func numberOfSections(in tableView: UITableView) -> Int {
return (hitsSource?.numberOfSections())!
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (hitsSource?.numberOfHits(inSection: section)) ?? 0
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
let sectionTitle = UILabel(frame: CGRect(x: 0, y: 0, width: headerView.frame.width, height: headerView.frame.height))
Utilities.styleLblFont(sectionTitle, fontName: "Cabin-SemiBold", fontSize: 18, color: .black)
var headertitle = ""
switch section {
case 0:
print("section 0")
headertitle = "People"
case 1:
print("section 1")
headertitle = "Businesses"
default:
break
}
headerView.backgroundColor = .white
sectionTitle.text = headertitle
headerView.addSubview(sectionTitle)
return headerView
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! ResultsItemCell
switch indexPath.section {
case 0:
if let item: User = try? hitsSource?.hit(atIndex: indexPath.row, inSection: indexPath.section) {
cell.nameLbl.text = item.userName
cell.avatar.loadImage(item.profilePhotoUrl)
cell.jobTitleLbl.text = item.userType
}
case 1:
if let business: Business = try? hitsSource?.hit(atIndex: indexPath.row, inSection: indexPath.section) {
cell.nameLbl.text = business.businessName
cell.avatar.loadImage(business.businessAvatarUrl)
cell.jobTitleLbl.text = business.mission
}
default:
break
}
return cell
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
let seeResultsBtn = UIButton(frame: CGRect(x: 0, y: 0, width: footerView.frame.width, height: footerView.frame.height))
var footerTitle = ""
switch section {
case 0:
footerTitle = "See all people"
case 1:
footerTitle = "See all businesses"
default:
footerTitle = "See all results"
}
footerView.addSubview(seeResultsBtn)
seeResultsBtn.translatesAutoresizingMaskIntoConstraints = false
seeResultsBtn.centerYAnchor.constraint(equalTo: footerView.centerYAnchor).isActive = true
seeResultsBtn.centerXAnchor.constraint(equalTo: footerView.centerXAnchor).isActive = true
Utilities.buttonTitle(seeResultsBtn, title: footerTitle, titleColour: .jabiMain, fontName: "Cabin-Medium", fontSize: 16)
footerView.backgroundColor = .white
return footerView
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 50
}
}
Please see the screenshots:

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

UITableViewCell ignore autolayout

I tried everything since few days, even I tried use automaticDimension, and estimatedRowHeight = 44, none of them luck. I am new to UITableView and I am tried to practice with it. I look everywhere in stackoverflow, etc and no luck. I am not sure what did I doing wrong with this code below.
In view controller:
reminder_tableview.frame = view.bounds
reminder_tableview.allowsSelection = false
reminder_tableview.estimatedRowHeight = 44
reminder_tableview.rowHeight = UITableView.automaticDimension
reminder_tableview.register(reminder_tableCell.self, forCellReuseIdentifier: "reminderList")
reminder_tableview.delegate = self
reminder_tableview.dataSource = self
tab2_body.addSubview(reminder_tableview)
And in the extension for UITableViewDelegate, UITableViewDataSource:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {return UITableView.automaticDimension}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {return 50.0}
func numberOfSections(in tableView: UITableView) -> Int {return reminder_category_user.count}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if reminder_tableisready == true {
let category_name = UILabel()
category_name.frame = CGRect(x: 20, y: 0, width: view.frame.width - 80, height: 50)
category_name.font = UIFont(name: "Arial", size: 30)
category_name.text = reminder_category_user[section]
category_name.textColor = UIColor.red
let num_of_reminder = UILabel()
num_of_reminder.frame = CGRect(x: view.frame.width - 75, y: 0, width: 70, height: 50)
num_of_reminder.font = UIFont(name: "Arial", size: 30)
num_of_reminder.text = String(reminder_final_table[section].count)
num_of_reminder.textAlignment = .right
num_of_reminder.textColor = UIColor.red
let headerView = UIView()
headerView.addSubview(category_name)
headerView.addSubview(num_of_reminder)
return headerView
} else {
return UIView()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if reminder_tableisready == true {
return reminder_final_table[section].count
} else {
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reminderList") as! reminder_tableCell
cell.backgroundColor = UIColor(red: 15/255, green: 15/255, blue: 15/255, alpha: 1)
cell.frame.size.height = 100
cell.textLabel?.text = reminder_final_table[indexPath.section][indexPath.row].pre_title
cell.textLabel?.font = UIFont(name: "Arial", size: 18)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.lineBreakMode = .byWordWrapping
cell.textLabel?.sizeToFit()
let getdatefromdatedue = Date(timeIntervalSince1970: TimeInterval(reminder_final_table[indexPath.section][indexPath.row].pre_datedue))
let duedateformat = DateFormatter()
duedateformat.dateFormat = "MMMM d, yyyy\nh:mm a"
if reminder_final_table[indexPath.section][indexPath.row].pre_datedue != 0 {
cell.layoutMargins.right = 160
cell.reminder_date_due.text = "Date Due\n\(duedateformat.string(from: getdatefromdatedue))"
} else {
cell.reminder_date_due.text = ""
}
cell.reminder_date_due.textColor = UIColor.red
cell.reminder_date_due.textAlignment = .right
cell.reminder_date_due.numberOfLines = 4
cell.reminder_date_due.font = UIFont(name: "Arial", size: 15)
return cell
}
}
class reminder_tableCell: UITableViewCell {
var reminder_date_due = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
reminder_date_due.frame = CGRect(x: reminder_tableview.frame.width - 155, y: 0, width: 150, height: 66)
addSubview(reminder_date_due)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
First I'd highly recommend against using magic values for your frames (and in general), because if you want to support all screen sizes it will quickly get out of control.
Also, since you're pre-calculating all your frames, you're actually not using Auto Layout.
Apple has introduced Auto Layout with a constraint-based behaviour so that you don't have to do all of the frame calculation work manually for every screen size. Auto Layout will dynamically calculate the size and position for all your views depending on the constraints/anchors you've set, whether it is by using Storyboards or programatically.
I also see in your code that you're using a reminder_tableview variable thats refers to your UITableView and makes me think you're using your table view as a global property: something which you should be avoiding at all cost.
For naming properties or methods, the best practice would be to use Camel Case, as it will make your code easier to read and understand. Camel Case is when you start a name with a lowercase letter, then capitalize the first letter of second and all subsequent words, for example:
let reminderTableIsReady = false
var reminderDueDateLabel: UILabel?
func scheduleNewReminder() {}
// ...
And the common accepted way for naming classes, enums or structs would be Upper Camel Case:
class ReminderTableViewCell: UITableViewCell {}
Now to go back to your code, I refactored and made a minimal version of it for you to see it how it would work with Auto Layout and constraints, for the UITableView and the UITableViewCell.
I didn't add everything from your code but I think you can easily do the rest by yourself:
ReminderViewController:
import UIKit
// You don't have to use this Date extension below, but this will improve the performances by keeping only one formatter instance, since you will be reusing it in all your UITableViewCell:
extension Date {
static let formatter = DateFormatter()
func formatted() -> String {
Date.formatter.dateFormat = "MMMM d, yyyy, hh:mm a"
return Date.formatter.string(from: self)
}
}
struct Reminder {
let dueDate: TimeInterval
}
class ReminderViewController: UIViewController {
private let reuseIdentifier = "reuseIdentifier"
private var reminders = [[Reminder]]()
private let tableView = UITableView(frame: .zero, style: .grouped)
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupConstraints()
}
func setupViews() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(ReminderTableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
view.addSubview(tableView)
reminders = // ... Set your data here
}
func setupConstraints() {
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
}
extension ReminderViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
}
extension ReminderViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return reminders.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section > reminders.count {
return 0
}
return reminders[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ReminderTableViewCell
let reminder = reminders[indexPath.section][indexPath.row]
let date = Date(timeIntervalSince1970: reminder.dueDate)
if reminder.dueDate > 0 {
cell.dueDateLabel.text = "Due Date: \(date.formatted())"
}
return cell
}
}
ReminderTableViewCell:
class ReminderTableViewCell: UITableViewCell {
let dueDateLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
setupConstraints()
}
func setupViews() {
dueDateLabel.textColor = .red
dueDateLabel.textAlignment = .right
dueDateLabel.numberOfLines = 4
dueDateLabel.font = UIFont(name: "Arial", size: 15)
contentView.addSubview(dueDateLabel)
}
func setupConstraints() {
dueDateLabel.translatesAutoresizingMaskIntoConstraints = false
dueDateLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
dueDateLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20).isActive = true
dueDateLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
dueDateLabel.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20).isActive = true
}
required init?(coder: NSCoder) {
fatalError()
}
}
Your question is little bit incomplete, I assume your issue is reminder_date_due label is not getting the layout as expected.
First of all you are not using autolayout, you are using frame based layout system.
You're setting the label's frame in init but at that point layout engine hasn't properly calculated the parent view's frame hence reminder_tableview.frame.width is invalid.
Try setting label's frame in func layoutSubviews().

Resources