How does iOS call delegates and datasource methods? - ios

For example, when we are creating tableview we need some datasource methods like
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messageArray.count
}
I don't call this anywhere. However, iOS does this instead of me and I wonder how iOS does this?
iOS search for a tableview, if it is available on the view then call delegates and datasource methods or it called when we declare uiTableView.delegate = self or uiTableView.datasource = self.
Another is these methods called before viewDidLoad?

Generally speaking, this is the setup for a class with a delegate:
class SimpleTableView {
var delegate: SimpleTableViewDelegate?
// ...
func renderCell(at row: Int) { // called whenever the table needs to render a cell
let cell = SimpleTableViewCell()
cell.frame.size.height = delegate?.tableView(self, cellHeightForRow: row)
// continue rendering cell
}
}
The protocol SimpleTableViewDelegate contains the delegate methods. It would look something like this:
protocol SimpleTableViewDelegate {
func tableView(_ tableView: SimpleTableView, cellHeightForRow: Int) -> CGFloat
}
So what we have here is a class, SimpleableView, that gets data from somewhere (the view controller). This is how the delegate comes into play:
class ViewController: UIViewController, SimpleTableViewDelegate {
var tableView = SimpleTableView()
override func viewDidLoad() {
tableView.delegate = self
}
func tableView(_ tableView: SimpleTableView, cellHeightForRow: Int) -> CGFloat {
return 44
}
}
This is essentially how a delegate works, and that is what the real tableView is doing. You set the tableView delegate and tableView calls the delegate methods to get information from you.
Hopefully this helps explain to you how the delegate works here, what calls it, and what's going on in general. If you need clarification, don't hesitate to ask!

So a quick way to look at this is separate these two things.
First lets look at delegate
https://developer.apple.com/documentation/uikit/uitableviewdelegate
The delegate provides a set of methods that you can include in your code that provides a callback for uitableview to execute certain protocol defined methods depending on whats happening within the tableView.
example func tableView(UITableView, heightForRowAt: IndexPath)
This example allows uitableview to ask you how should i display a certain cell at this current indexpath.
Next lets look at the datasource
https://developer.apple.com/documentation/uikit/uitableviewdatasource
Datasource works in a similar fashion as the delegate but provides a different set of methods that help you populate your table view.
example func numberOfSections(in: UITableView)
Apple's uitableview will call this method and ask the tableview how many sections should I display.
Ultimately, these are just protocols that allow the tableview to interact with your code and helping you display your table with your configuration!

tableView:numberOfRowsInSection is method of the UITableViewDatasource protocol. The methods of data source are called by method reloadData() of UITableView.
According to documentation of UITableView:
UITableView overrides the layoutSubviews() method of UIView so that it
calls reloadData() only when you create a new instance of UITableView
or when you assign a new data source. Reloading the table view clears
current state, including the current selection. However, if you
explicitly call reloadData(), it clears this state and any subsequent
direct or indirect call to layoutSubviews() does not trigger a reload.

Related

Protocol/delegate in Swift3 doesn't work

I have followed this tutorial to have delegate methods to update a value in my other class, but it does not even trigger it. Can you please tell me what i am doing wrong?
protocol myDelegate {
func report(info:String)
}
class TypeFilterViewController: UIViewController, XLFormRowDescriptorViewController,
UITableViewDataSource, UITableViewDelegate {
var delegate:myDelegate?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.delegate?.report("testValue")
self.navigationController?.popViewControllerAnimated(true)
}
}
So, after i select the row item, i dismissed pushed view and display previous class.
class SearchRefinementsTypeCell: XLFormBaseCell, XLFormBaseCellSeparator, myDelegate {
// Delegate method
func report(info: String) {
print("delegate: \(info)")
}
override func update() {
super.update()
let mc = TypeFilterViewController()
mc.delegate = self
self.headerLabel.text = //Value from TypeFilterViewController didSelectRow
}
Thank you for all kind of helps.
You clearly misunderstood the tutorial.
Delegate pattern is useful when you want to delegate from a cell to view controller. You're doing the opposite: sending event from a viewController to a cell, which is pointless, since your viewController already has access to it's tableView, which in it's turn operates with it's cells.
Also you shouldn't use any ViewControllers inside cell class because it breaks MVC pattern. You should think of UITableViewCell and pretty much every UIView as of powerless objects which cannot decide anything by themselves, but can only delegate events to other smart guys, which do the logic by themselves (view controllers).
Now about your case:
You have vc A and vc B, pushed over it. When a cell in B is pressed, you should send a callback to A, right? What you should do:
B has a delegate which implements some protocol
When A pushes B, it set's itself as a protocol: b.delegate = self
When a cell is selected in B, you call delegate's method, which is implemented in A and passes a string into it.
UI in A is updated.
Once again, cells must not know anything about any of your view controllers, they are just pawns. All logic should be handled between view controllers themselves.

Swift View Controller with UITableView sections

I've been searching for awhile without luck. I am trying to find an example of a View Controller with a UITableView that has sections. The examples I've see are all dealing with a Table View Controller which I cannot use as I have need of buttons in the same view which control the content of the table view. Anyone have an example, know of an example or have an idea about to implement such? Thanks.
Edit
I've got a table view in a view controller, get the data from an api call, separate the sections and data in an array of a struct. I then send this to be bound to the table view. Doing so throws
[UIView tableView:numberOfRowsInSection:]: unrecognized selector sent to instance
but I don't understand where the problem is.
Code for the tablview
//MARK: Tableview delegates
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let count = incidentDataSection?.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (incidentDataSection?.count)! > 0{
return incidentDataSection![section].incidents.count
}
return 0
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
/*
func tableView(tableView: UITableView, iconForHeaderInSection section: Int) -> UIImage? {
return incidentDataSection?[section].icon
}*/
//if clicked, will openn details view passing in the details
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let incidentDetails = incidentData?[indexPath.row]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let section = incidentDataSection?[indexPath.section] {
let cell = tableView.dequeueReusableCell(withIdentifier: "IncidentTableViewCell") as! IncidentTableViewCell
cell.roadNameLabel.text = section.incidents[indexPath.row].RoadWay
cell.whenLabel.text = section.incidents[indexPath.row].DateCreated
cell.statusLabel.text = section.incidents[indexPath.row].DateCleared
return cell
}
return UITableViewCell()
}
incidentDataSection is an array of a struct which has the section title and the different items.
Answer
Though I received some fairly good feedback, the cause was actually a typo. Looking closely at
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
you'll notice the problem is that there is no underscore before tableView:. What was happening is that the datasource and delegate were skipping over the functions since with and without call different protocols in swift 3. Thanks to thislink I was able to figure out the cause. My bad for forgetting to mention this was in Swift 3. Might had saved everyone some time.
You need a tableview instance in your view controller.
Implement the protocols UITableViewDelegate, UITableViewDataSource in your view controller as a UITableViewController.
Don't forget bind the tableview in XIB with tableview in the class.
Look this sample:
class Sample01ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.tableView?.reloadData()
}
// ...
You have the required methods implemented, however it sounds like you need to "subclass" or "subcribe" to the UITableView's delegate and dataSource. By using:
class MyViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView : UITableView!
}
Now that you have those protocols you will need to set your tableView's delegate and dataSource to your viewController. You can do this using storyboard by drag and drop, or inside of your viewDidLoad() which is what I always do because it is easy for other developers to see from the start of opening your code where your delegate and dataSources are assigned to. Using:
#override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
Then your delegate methods and dataSource methods in your viewcontroller will be called for that tableView. Then you can add the IBOutlets to UIButton/UILabel/UISwitch, etc... and do what you will with your ViewController without being limited to simply using a table view inside of that view controller. I Almost always use this methods when using UITableViews/UICollectionViews even if I set the tableView/collectionView to be the size of the whole view because I like the freedom of using a UIViewController over a UITableViewController/UICollectionViewController.
*Note numberOfRows() is not required but I always override it as well, just kind of a habit at this point. Also you sound new to iOS development, so if you aren't already, the next thing I would look into after getting your tableView up and running is pulling your data from your API on a background thread to keep your mainThread open for user response on your UI, DispatchQueue. This is really important if you are displaying images from the API.

Dynamic DataSource swap for UITableView in Swift

I am new to iOS/Swift development, and having a problem with making a dynamic swap of DataSource work for a UITableView - note I am not swapping the Delegate, just the DataSource.
I have read other similar questions/responses on Stack Overflow, and not found one that's relevant to my situation. Typically they're about setting the DataSource on "viewDidLoad" (e.g. this one, and this one), whereas my situation is about swapping the DataSource when the user presses a button. The problems in the referenced questions don't exist in my code.
Here's outline of my code. I have the buttonPress method connected to the TouchUpInside event in the storyboard:
class ViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
...
#IBAction func buttonPress(sender: UIButton) {
...
self.tableView.dataSource = DummyDataSource()
self.tableView.delegate = self
self.tableView.reloadData()
}
...
}
...and here's my datasource class:
import UIKit
class DummyDataSource: NSObject, UITableViewDataSource {
let names = ["A", "B", "C"]
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier(simpleTableIdentifier) as UITableViewCell?
if ( cell == nil ) {
cell = UITableViewCell( style: UITableViewCellStyle.Default,
reuseIdentifier: simpleTableIdentifier)
}
cell!.textLabel?.text = names[indexPath.row]
return cell!
}
}
When I press the button, I can see that the pressButton method is being called correctly, but the data doesn't show up in the tableView (no errors - just no data). Any ideas please? Thank you.
UITableView's dataSource property is either unsafe_unretained or weak, depending on which version of iOS. Either way, as with any other delegate, it doesn't keep a strong reference.
So when you write a line like this:
self.tableView.dataSource = DummyDataSource()
Your newly instantiated DummyDataSource() property doesn't have any strong references pointing to it. It is therefore immediately released by ARC.
We need to keep a strong reference to the data source if we want it to stick around.
My recommendation would be to add a data source property to your view controller which can keep the strong reference. We will also use the didSet of this property to set the table view's data source property and reload its data.
var dataSource: UITableViewDataSource? {
didSet {
tableView?.dataSource = dataSource
tableView?.reloadData()
}
}
We use optional-chaining to protect against the data source being set before the view is loaded and the tableView property is populated. Otherwise, we will get a fatal error for trying to unwrap nil.
We shouldn't need to be setting the data source property on the table view anywhere else. And the only reason why we should need to called reloadData() anywhere else is if our data source itself can change the data it is representing. However, it is important that reloadData() is called in sync with resetting the dataSource to protect against some likely index-out-of-bound crashes.

Having an issue grabbing the index of selected viewtable in swift

I have been learning swift through the last few days and I have come across an error that I have been stuck on for quite a while now.
I am attempting to get the selected indexPath so that I can then push data according to which item he selected. I have searched through and tried many different solutions I have found on stack overflow as well as different websites but I am not able to get this figured out still.
The code is below:
#IBOutlet var selectGroceryTable: UITableView!
/* Get size of table */
func tableView(tableView: UITableView, numberOfRowsInSection: Int) ->Int
{
return grocery.count;
}
/* Fill the rows with data */
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let myCell:UITableViewCell = selectGroceryTable.dequeueReusableCellWithIdentifier("groceryListRow", forIndexPath:indexPath) as! UITableViewCell
myCell.textLabel?.text = grocery[indexPath.row];
myCell.imageView?.image = UIImage(named: groceryImage[indexPath.row]);
return myCell;
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
print("Row Selected");
NSLog("Row Selected");
}
Nothing ever prints acting like the function is not being called. However, I do not understand why this would not be called?
Any help would be greatly appreciated!
UPDATE:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
selectGroceryTable.data = self;
selectGroceryTable.delegate = self; //gives error states you can not do this
}
There are a couple of things to check in cases like this:
First, what kind of method is didSelectRowAtIndexPath?
Answer: It's a UITableViewDelegate method. Did you set your view controller up as the delegate of the table view? If not, this method won't get called.
Second, have you made absolutely certain that the method signature is a perfect match for the method from the protocol? A single letter out of place, the wrong upper/lower case, a wrong parameter, and it is a different method, and won't be called. it pays to copy the method signature right out of the protocol header file and then fill in the body to avoid minor typos with delegate methods.
It looks to me like your method signature is correct, so my money is on forgetting to set your view controller up as the table view's delegate.

Do I need to call super when overriding data source and delegate methods in UITableViewController?

Consider the following snippet that derives a custom view controller from UITableViewController.
class Controller: UITableViewController {
...
)
// MARK: - Table View Data Source
extension Controller {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// call super?
...
}
}
// MARK: - Table View Delegate
extension Controller {
override func tableView(tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: NSIndexPath) {
// call super?
...
}
}
The documentation says:
You may override loadView or any other superclass method, but if you do be sure to invoke the superclass implementation of the method, usually as the first method call.
My question is, does this also apply to the methods in the protocols UITableViewDataSource and UITableViewDelegate to which UITableViewController conforms?
Calling super in data source methods does not make much sense to me since usually you define your own content using these methods. I am, however, not sure about the delegate methods. Calling super in willBeginEditingRowAtIndexPath for instance does not seem to have any obvious effect though.
There is no need to call super in these cases. The documentation quote you included in your original question refers to overriding the super class methods. However, UITableViewDataSource and UITableViewDelegate are protocols (and not your class' superclass) and the methods you mentioned were declared especially for you to implement them the way that fits you best.
You're right, it doesn't make sense to do it in this case and is not something you should be doing.

Resources