UItableView delegate called mutiple times in swift - uitableview

This is my custom UITableViewController, I added any other code and the delegates methods are called five times
import UIKit
class SettingsViewController: UITableViewController {
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
print("numberOfSectionsInTableView called")
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRowsInSection called for section => \(section)")
return 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: nil)
cell.textLabel?.text = "hey"
return cell
}
}
the logs:
numberOfSectionsInTableView called
numberOfSectionsInTableView called
numberOfRowsInSection called for section => 0
numberOfSectionsInTableView called
numberOfRowsInSection called for section => 0
numberOfSectionsInTableView called
numberOfRowsInSection called for section => 0
numberOfSectionsInTableView called
numberOfRowsInSection called for section => 0
numberOfSectionsInTableView called
numberOfRowsInSection called for section => 0
EDIT
it's the only class which I launch from the appDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
if let window = window {
let nav = UINavigationController()
nav.viewControllers = [Test()]
window.rootViewController = nav
window.makeKeyAndVisible()
}
return true
}

Table view data source methods may get called multiple times. There is no guarantee on number of times it gets called. Due to the the implementation of UITableView there are many situations where certain delegate/datasource methods will get called twice or more because the table view has to refresh something. This includes situations like reloading table view, changing table header view etc. So, you might want to check your implementation on such cases!

Related

UITableViewDiffableDataSource: how to get a section index

I'm trying to adopt the new iOS 13 UITableViewDiffableDataSource and I've hit a snag; I can't work how to implement
func sectionIndexTitles(for tableView: UITableView) -> [String]?
That's a data source method, not a delegate method. So, now that the data source is the UITableViewDiffableDataSource, it needs to implement that method. But it doesn't.
I tried subclassing UITableViewDiffableDataSource and adding an implementation of sectionIndexTitles, but my implementation was never called:
class MyDataSource : UITableViewDiffableDataSource<String,String> {
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return self.snapshot().sectionIdentifiers // not called
}
}
Has anyone solved this one? I'll file it as a bug just in case.
You need to subclass UITableViewDiffableDataSource and then overwrite
func sectionIndexTitles(for tableView: UITableView)
by yourself.
To enable the index' functionality though, you have to also overwrite
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int)
Here is an example how I implemented it:
import UIKit
import MediaPlayer
class TableViewDiffableDataSource: UITableViewDiffableDataSource<String, MPMediaItemCollection> {
var sectionTitles = [String]()
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return sectionTitles
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return sectionTitles.firstIndex(of: title) ?? 0
}
}
Credits go to Steve Breen, who led me in the right direction.
After you init self.dataSource to UITableViewDiffableDataSource (which sets itself to the tableView.dataSource) set the tableView.dataSource back to self, i.e. the UITableViewController subclass. Now in your numberOfSectionsInTableView and numberOfRowsInSection methods forward those to self.dataSource and return its info (this is the composition pattern). Now your UITableViewController just implements its section titles as normal since it is the table's data source.
I believe UITableViewDiffableDataSource should not be setting itself as the dataSource if one is already set but I guess they designed it to work in the least error prone way because with UITableViewController added to a storyboard it's set as the dataSource and delegate by default.

Static TableView gets NSRangeException

I have a UITableViewController with static cells, and when I run the app and click on the button that leads to it, I get a SIGABRT signal in AppDelegate.
I tried to find unused outlets, but it didn't work.
Here is the Console Log:
The UITableViewController Code:
import UIKit
import os.log
class SettingsTableViewController: UITableViewController {
// MARK: Properties
#IBOutlet weak var noteDisplayKindSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
notedisplayKindSwitch.setOn(Settings._displaynotesAsNames, animated: false)
}
#IBAction func ChangeNoteDisplayKind(_ sender: UISwitch) {
Settings._displayNotesAsNames = sender.isOn
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
}
The UITableViewController in the Storyboard:
The Connections:
What am I doing wrong?
Thanks.
You are using static cells. There is no reason to implement numberOfSections and numberOfRowsInSection because those are specified by the static layout in the Storyboard.
Because you have implemented:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
you are telling iOS that every section has 3 rows, which is a lie. So iOS tries to access the second row of your first section and crashes with array index out of range because that section has just 1 row.
So, delete the implementations of numberOfSections and numberOfRowsInSection and you should be good to go.
Read the error message, and please do not post images of code.
Somewhere in your tableView:cellforRowAtIndexPath method, there's an empty array but you're trying to access the element at index 1.

willDisplayHeaderView being called but text attributes in titleForHeaderInSection remain unchanged

I have a custom tableView with the willDisplayHeaderView below.
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let tableViewHeaderFooterview = UITableViewHeaderFooterView()
tableViewHeaderFooterview.textLabel?.font = UIFont.hmFont(13)
tableViewHeaderFooterview.textLabel?.textColor = UIColor.hmDarkGrey()
}
I have another tableViewController that subclasses from the custom tableView with the titleForHeaderInSection below.
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "YOUR SUBSCRIPTION"
} else if section == 1 {
return "SIGN OUT"
}
return nil
}
My willDisplayHeaderView is called but I am not seeing the changes in the header view titles when I run my app on simulator. I have been searching for answers the past couple hours on here but so far no solution has worked. I thought at first I might be forgetting to set my delegate/datasource but that didn't work.
Let's take a look at your code
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let tableViewHeaderFooterview = UITableViewHeaderFooterView()
tableViewHeaderFooterview.textLabel?.font = UIFont.hmFont(13)
tableViewHeaderFooterview.textLabel?.textColor = UIColor.hmDarkGrey()
}
In the function declaration you've got a view called view that your function is being called with. In other words, iOS is looking up this function on your class and saying, "I'm about to show this view, just so you know." In the first line of your implementation though, you're creating a new view called tableViewHeaderFooterview and setting some properties on it. Those changes are not going to be reflected in the view that iOS is telling you about.
At this point you have a few choices: 1) muck around with view's subviews looking for labels (messy and bad), 2) try to cast it to UITableHeaderFooterView (if let header = view as? UITableViewHeaderFooterView { header.textLabel.font = whatever }), or 3) register your own UIView subclass and configure it how you want ahead of time:
class MyHeader: UITableViewHeaderFooterView {
...
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(MyHeader.self, forHeaderFooterViewReuseIdentifier: "myHeader")
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "myHeader") as? MyHeader {
myHeader.label.text = "YOUR SUBSCRIPTION";
return myHeader
}
return nil
}

Prepare number of sections and row before UITableViewController gets called

I have a subclass of UITableViewController where I have 2 properties that will defines the number of sections and number of rows in section
It seems the the delegate methods numberOfSectionsInTableView(tableView: UITableView) -> Int get called before viewDidLoad() which is where I am initialising the 2 properties at currently. Therefore the 2 properties does not have any value when the delegate methods get called.
Where should I initialise these 2 value such that it will be ready for the delegate method.
It shouldn't be calling that delegate func before viewDidLoad.
I just tested the following code:
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("whatt????")
}
When I run my app, this prints:
viewDidLoad
whatt???
whatt???
whatt???
I would test that your variables are properly initialized. If they are, you should have no problem using them to define how many rows/sections you have.

SWIFT: Prototype Cells not being loaded after performSegueWithIdentifier

I have a ViewController that calls (clicking on a button) another View using this function
#IBAction func btnSeeContact(sender: AnyObject) {
self.performSegueWithIdentifier("segueSeeContact", sender: self)
}
and my prototype cell is "linked" to a custom View Controller named ContactsTableViewCell that I have created and it implements:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! ContactsTableViewCell
cell.txtName.text = "test"
cell.txtPhone.text = "1234567890"
return cell
}
When I run the project, the button calls the table, but there is no Cell on it, and I put a breakpoint on those tableView functions and they are not being reached.
What am I missing here that those functions are never being called?
I am adding a new answer since my previous answer was up voted, so I don't want to make massive edits that one, and is still a valid way to fix your issue.
The issue is you have your custom classes confused. In your screen shot you can see that the the Table View Controller is not set to a custom class, it just says Table View Controller. That is the object that needs to get a custom implementation of the UITableViewController class.
Instead you seem to be setting the cell's class to a custom class, and implementing the delegate methods there. You still need a custom class for the table view cell, but it should be a custom class of UITableViewCell.
So your cell class should look something like this:
import UIKit
class YourCustomTableViewCell: UITableViewCell {
#IBOutlet weak var yourLabel1: UILabel!
#IBOutlet weak var yourLabel2: UILabel!
}
You will be given an instance of this cell to configure in cellForIndexPath.
So your Table view controller class should be set to a class that looks like below. The YourTableViewController is were you want to implement all the delegate methods.
Note: if you are using a UITableViewController dragged out from the storyboard, it will already have the tableView, and delegate / data source stuff already wired up for you. You will also notice that you are overriding the delegate methods as the UITableViewController class has default implementations of these. If you are just using a normal view controller, then see my previous answer for more details on how to set that up.
import UIKit
class YourTableViewController: UITableViewController {
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
if let cell = cell as? YourCustomTableViewCell {
cell.yourLabel1.text = "some text"
cell.yourLabel2.text = "some other text"
}
return cell
}
}
As others have commented, you really need to provide a little more context.
Here are a few things that might be going wrong, providing more context would confirm or deny this guesses.
First you don't show the numberOfSectionsInTableView method.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
I think you would need to provide a value other than 0
Secondly, since I don't see override in front of what I am sure you are intending to be UITableViewDelegate methods function calls, that means your view controller is not a UITableViewController. This makes me wonder if you defined this view controller as conforming to the UITableViewDelegate protocol and if you set the table view outlet delegate to self. (or even wired up the UITableView to an outlet)
If you use a plain UIViewController to host a table view you need to do the following:
Wire up your UITableView to an outlet in your view controller
Declare the view controller as conforming to the UITableViewDeleagate (and maybe UITableViewDataSource) protocol
set the table view's outlet delegate (and maybe dataSource) properties to self (the view controller implementing the protocols)
Implement the required methods
So something like this:
class MyTableViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("your PrototypeCell", forIndexPath: indexPath)
// Configure the cell...
return cell
}
}

Resources