I have a table view that is subclassed and extended, then being set up in the View controller. The problem that I'm having is the delegate method ViewForHeaderInSection isn't being called, while the normal data source methods are being called.
(this is the TableView setup method, is called in ViewDidLoad. The table view is connected to the View Controller with IBOutlet)
func setup() {
self.dataSource = self as UITableViewDataSource
self.delegate = self
let nib = UINib(nibName: "MyTableViewCell", bundle: nil)
self.register(nib, forCellReuseIdentifier: "MyCell")
}
These are the extensions
extension MyTableView: UITableViewDataSource,UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
//print(Genres.total.rawValue)
return Genres.total.rawValue
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let tableSection = Genres(rawValue: section), let articleData = articleDictionary[tableSection] {
// print(articleData.count)
return articleData.count
}
print(0)
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyTableViewCell
if let tableSection = Genres(rawValue: indexPath.section), let article = articleDictionary[tableSection]?[indexPath.row]{
cell.cellTitle.text = article.articleTitle
cell.cellImageView.image = article.articleImage
cell.cellEmojiReaction.text = article.articleEmojis
}
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 40.0))
view.backgroundColor = .brown
let label = UILabel(frame: CGRect(x: 16, y: 0, width: tableView.bounds.width, height: 40))
label.textColor = .blue
let tableSection = Genres(rawValue: section)
switch tableSection {
case .breakingNews?:
label.text = "Breaking News"
case .scienceAndTech?:
label.text = "Science and Tech"
case .entertainment?:
label.text = "Entertainment"
case .sports?:
label.text = "Sports"
default:
label.text = "Invalid"
}
print("Function Run")
view.addSubview(label)
print(label.text ?? "Nothing Here")
return view
}
}
Here is the View controller Code:
class ViewController: UIViewController {
#IBOutlet weak var myTableView: MyTableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView.setup()
}
}
Is there any specific reason why the delegate method isn't being called? Thank you in advance for your time.
For this you have to also implement one more method heightForHeaderInSection along with viewForHeaderInSection method.
If you are using viewForHeaderInSection delegate method then it is compulsory to use heightForHeaderInSection method other wise section header mehtod is not called
Prior to iOS 5.0, table views would automatically resize the heights
of headers to 0 for sections where
tableView(_:viewForHeaderInSection:) returned a nil view. In iOS 5.0
and later, you must return the actual height for each section header
in this method.
Official Link for more description https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614855-tableview
Add in viewDidLoad:
tableView.estimatedSectionHeaderHeight = 80
Related
I'm setting up a collapsable tableView, but something strange happens on the collapsable item. When you look at the video keep an eye on the "Where are you located" line.. (I'm using a .plist for the question and answer items)
Where do I go wrong, is it somewhere in my code? I don't want to let that line stick on the top :(
Here is the code I'm using but I can't find anything strange...
class FAQViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var questionsArray = [String]()
var answersDict = Dictionary<String, [String]>() // multiple answers for a question
var collapsedArray = [Bool]()
#IBOutlet weak var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
// Hide the navigation bar on the this view controller
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
addTableStyles()
readQAFile()
tableView.delegate = self
tableView.dataSource = self
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
func addTableStyles(){
navigationController?.isNavigationBarHidden = false
self.tableView?.backgroundView = {
let view = UIView(frame: self.tableView.bounds)
return view
}()
tableView.estimatedRowHeight = 43.0;
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = UITableViewCell.SeparatorStyle.singleLine
}
func readQAFile(){
guard let url = Bundle.main.url(forResource: "QA", withExtension: "plist")
else { print("no QAFile found")
return
}
let QAFileData = try! Data(contentsOf: url)
let dict = try! PropertyListSerialization.propertyList(from: QAFileData, format: nil) as! Dictionary<String, Any>
// Read the questions and answers from the plist
questionsArray = dict["Questions"] as! [String]
answersDict = dict["Answers"] as! Dictionary<String, [String]>
// Initially collapse every question
for _ in 0..<questionsArray.count {
collapsedArray.append(false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return questionsArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if collapsedArray[section] {
let ansCount = answersDict[String(section)]!
return ansCount.count
}
return 0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
// Set it to any number
return 70
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if collapsedArray[indexPath.section] {
return UITableView.automaticDimension
}
return 2
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x:0, y:0, width:tableView.frame.size.width, height:40))
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 10, y: 10, width: tableView.frame.size.width, height: 50)) as UILabel
headerString.text = "\(questionsArray[section])"
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:#selector(sectionHeaderTapped(_:)))
headerView.addGestureRecognizer(headerTapped)
return headerView
}
#objc func sectionHeaderTapped(_ recognizer: UITapGestureRecognizer) {
let indexPath : IndexPath = IndexPath(row: 0, section:recognizer.view!.tag)
if (indexPath.row == 0) {
let collapsed = collapsedArray[indexPath.section]
collapsedArray[indexPath.section] = !collapsed
//reload specific section animated
let range = Range(NSRange(location: indexPath.section, length: 1))!
let sectionToReload = IndexSet(integersIn: range)
self.tableView.reloadSections(sectionToReload as IndexSet, with:UITableView.RowAnimation.fade)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cellIdentifier = "Cell"
let cell: UITableViewCell! = self.tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
cell.textLabel?.adjustsFontSizeToFitWidth = true
cell.textLabel?.numberOfLines = 0
let manyCells : Bool = collapsedArray[indexPath.section]
if (manyCells) {
let content = answersDict[String(indexPath.section)]
cell.textLabel?.text = content![indexPath.row]
}
return cell
}
override var prefersStatusBarHidden: Bool {
return true
}
}
You need to change the style of the tableView to grouped, when you initialize it:
let tableView = UITableView(frame: someFrame, style: .grouped)
or from Storyboard:
After that you will have this issue, which I solved by setting a tableHeaderView to the tableView that has CGFloat.leastNormalMagnitude as its height:
override func viewDidLoad() {
super.viewDidLoad()
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
}
Just remove your headerView from view hierarchy here
#objc func sectionHeaderTapped(_ recognizer: UITapGestureRecognizer) {
headerView.removeFromSuperview()
...
}
By the way, yes creating a openable tableview menu with using plist is one of the methods but it could be more simple. In my opinion you should refactor your code.
booktable.frame = CGRect(x: 0, y: booktopview.bounds.height, width: screenWidth, height: screenHeight-booktopview.bounds.height-tabbarView.bounds.height)
booktable.register(UITableViewCell.self, forCellReuseIdentifier: "mycell")
booktable.dataSource = self
booktable.delegate = self
booktable.separatorColor = UIColor.lightGray
booktable.backgroundColor = UIColor.clear
booktable.separatorStyle = .singleLine
bookview.addSubview(booktable)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(tableView == booktable)
{
let cell1 = booktable.dequeueReusableCell(withIdentifier: "mycell")
for object in (cell1?.contentView.subviews)!
{
object.removeFromSuperview();
}
let img :UIImageView = UIImageView()
let lbl : UILabel = UILabel()
img.frame = CGRect(x: 15, y: 15, width: 80, height: 130)
img.image = imgarray[indexPath.row]
img.layer.borderWidth = 1.0
img.layer.borderColor = UIColor.lightGray.cgColor
cell1?.contentView.addSubview(img)
imgheight = img.bounds.height
lbl.frame = CGRect(x: img.bounds.width + 40, y: (imgheight+40-80)/2, width: booktable.bounds.width-img.bounds.width + 40 - 100, height: 80)
lbl.text = imgname[indexPath.row]
lbl.numberOfLines = 0
lbl.textAlignment = .left
lbl.font = UIFont(name: "Arial", size: 23)
lbl.textColor = UIColor.black
cell1?.selectionStyle = .none
cell1?.contentView.addSubview(lbl)
return cell1!
}
The code shown above is for book table, which sometimes scrolls like normal and sometimes not scrolling at all. I am doing all the code programatically. I have tested this on both simulators and devices but still the problem exists. Any help is appreciated...
Create Custom UITableViewCell, let's say it is ListTableCell
class ListTableCell: UITableViewCell {
#IBOutlet weak var lblTemp: UILabel!
#IBOutlet weak var imgTemp: UIImage!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I've created UITableViewCell with xib like this and bind IBOutlets
Let's say we have struct Model and array like this
struct Model {
let image : UIImage
let name: String
}
for i in 0...10 {
let model = Model(image: #imageLiteral(resourceName: "Cat03"), name: "Temp \(i)")
array.append(model)
}
Now on ViewController viewDidLoad() method,
tableView.register(UINib(nibName: "ListTableCell", bundle: nil), forCellReuseIdentifier: "ListTableCell")
Implement UITableViewDataSource methods like this,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListTableCell") as! ListTableCell
let model = array[indexPath.row]
cell.lblTemp.text = model.name
cell.imgTemp.image = model.image
return cell
}
FYI
For different tableviews, you can create different custom cell the same way and cellForRowAt indexPath and numberOfRowsInSection method will change appropriately.
Let me know in case of any queries.
UPDATE
Follow this and this to create CustomTableCell programmatically
I'm looking to create a subclass for a tableview that I would like to use throughout out all my app. My problem is when I set the fields variable the tableview is still empty. I'm assuming that the fields variable is not setting right when it is set in the new ViewController. Thank you for the help in advance.
MySubClass
import UIKit
import LGButton
class RquestTableViewController: UITableViewController {
var fields:[UIView]
override func viewDidLoad() {
super.viewDidLoad()
viewSetup()
}
func viewSetup(){
tableView.register(CustomFormCell.self, forCellReuseIdentifier: labelReuseCellIdentifier)
self.tableView.tableFooterView = UIView.init(frame: CGRect.zero)
}
}
//MARK: Delegate
extension RquestTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return fields.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.labelReuseCellIdentifier, for: indexPath) as! CustomFormCell
let itemView = fields[indexPath.row]
cell.viewPassed = itemView
cell.backgroundColor = .clear
if let _ = itemView as? UILabel {
cell.separatorInset = UIEdgeInsetsMake(0, cell.bounds.size.width, 0, 0)
return cell
}
if let _ = itemView as? UIButton {
cell.separatorInset = UIEdgeInsetsMake(0, cell.bounds.size.width, 0, 0)
return cell
}
if let _ = itemView as? LGButton {
cell.separatorInset = UIEdgeInsetsMake(0, cell.bounds.size.width, 0, 0)
return cell
}
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
}
MyNewViewController
class NewRquestViewController: RquestTableViewController {
lazy var password:JVFloatLabeledTextField = {
let v = JVFloatLabeledTextField()
v.placeholder = "Password"
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
fields = [password]
}
}
Have you verified that fields really isn't being set properly? I see no reason why it wouldn't be. But I do see another problem: in addition to overriding numberOfSections(in:) and tableView(_,cellForRowAt:), you have to override tableView(_,numberOfRowsInSection:). What you have now is numberOfSections(in:) returning the number of fields, but tableView(_,cellForRowAt:) is expecting to populate the rows with the fields. That means that tableView(_,cellForRowAt:) will be called only when section is 0, so at most you'd see only one field. Instead, change numberOfSections(in:) to return 1, and implement tableView(_,numberOfRowsInSection:) to return fields.count.
I'm trying to add UITableView from another class but the delegate methods are not being called. Only the numberOfRowsInSection method is being called. I know I can do this in the main ViewController, but I would like to do it from another class. Is it possible? This code is fully functional, just copy and paste into a project. Thank you!
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = Table()
t.create()
}
}
class Table: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let myArray: NSArray = ["First","Second","Third"]
func create() {
let barHeight = UIApplication.shared.statusBarFrame.size.height
let displayWidth = UIScreen.main.bounds.width
let displayHeight = UIScreen.main.bounds.height
let myTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
UIApplication.shared.delegate?.window??.rootViewController?.view.addSubview(myTableView)
myTableView.dataSource = self
myTableView.delegate = self
myTableView.backgroundColor = UIColor.red
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Num: \(indexPath.row)")
print("Value: \(myArray[indexPath.row])")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRowsInSection")
return myArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRowAt")
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
cell.textLabel!.text = "\(myArray[indexPath.row])"
return cell
}
}
When you run this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = Table()
t.create()
}
}
You have created a local variable t. As soon as you leave the viewDidLoad() function, t no longer exists - so there are no delegate methods to call.
Instead, create a class-level variable that will stick around:
class ViewController: UIViewController {
let t = Table()
override func viewDidLoad() {
super.viewDidLoad()
t.create()
}
}
I think it is not called due to it Tablview not added to self.view or else. so you need to add this line after setting of tableview.
func create() {
let barHeight = UIApplication.shared.statusBarFrame.size.height
let displayWidth = UIScreen.main.bounds.width
let displayHeight = UIScreen.main.bounds.height
let myTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
UIApplication.shared.delegate?.window??.rootViewController?.view.addSubview(myTableView)
myTableView.dataSource = self
myTableView.delegate = self
myTableView.backgroundColor = UIColor.red
self.view.addSubview(myTableView) //add this line
}
So, I have a few Swipe actions like delete, block, etc in my UITableView. I wanted to add headers to separate my two sections. So, I added a prototype cell, named it HeaderCell and then went to the view. I added one label, named headerLabe. My problem is that when I swipe for the actions, the header cells were moving as well, which looked bad. I researched, and found a solution to just return the contentView of the cell. However, when I do this, the label has not shown up. I have tried a dozen different solutions, and nothing has worked, so I have turned to SO. Can anyone help me?
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell : CustomHeaderTableViewCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! CustomHeaderTableViewCell
if section == 0 {
headerCell.headerLabel.text = "Thank You's"
} else if section == 1 {
headerCell.headerLabel.text = "Conversations"
}
return headerCell.contentView
}
Thanks so much.
You can use a section Header as #ozgur suggest.If you still want to use a cell.
Refer to this datasource method
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath = YourHeaderCellIndexPath{
return false
}
return true
}
check the following methods
In your UIViewController use the following
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! WishListHeaderCell
headerCell.lblTitle.text = cartsData.stores_Brand_Name
let imgVw = UIImageView()
imgVw.frame = CGRectMake(8, 18, 25, 25)
imgVw.image = UIImage(named: "location.png")
let title = UILabel()
title.frame = CGRectMake(41, 10, headerCell.viwContent.frame.width - 49, 41)
title.text = cartsData.stores_Brand_Name
title.textColor = UIColor.whiteColor()
headerCell.viwContent.addSubview(imgVw)
headerCell.viwContent.addSubview(title)
return headerCell.viwContent
}
In your UITableViewCell use the following
import UIKit
class HeaderCell: UITableViewCell {
#IBOutlet weak var viwContent: UIView!
#IBOutlet weak var imgIcn: UIImageView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.viwContent.backgroundColor = UIColor.grayColor()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//UITableViewCell
let headerCell = tableView.dequeueReusableCellWithIdentifier("headerCell") as! SecJobCCHeaderTableViewCell
// Cell Rect
var cellRect : CGRect = headerCell.frame
cellRect.size.width = screenBounds.width
// Header Footer View
let headerFooterView = UITableViewHeaderFooterView(frame : cellRect)
//Adding Gesture
let swipeGestRight = UISwipeGestureRecognizer(target: self, action:#selector(AddSecJobCostCentreViewController.draggedViewRight(_:)))
swipeGestRight.enabled = true
swipeGestRight.direction = UISwipeGestureRecognizerDirection.Right
headerFooterView.addGestureRecognizer(swipeGestRight)
// Update Cell Rect
headerCell.frame = cellRect
// Add Cell As Subview
headerCell.tag = 1000
headerFooterView.addSubview(headerCell)
// Return Header Footer View
return headerFooterView
}
func draggedViewRight(sender:UISwipeGestureRecognizer) {
// Swipe Gesture Action
let currentHeaderView = sender.view?.viewWithTag(1000) as! SecJobCCHeaderTableViewCell
}