Dynamic datasource/delegates for UITableView in swift
NSObject? doesn't conforms to UITableViewDelegate, neither to UITableViewDataSource. You should create your protocol like
protocol GeneralDataSource: UITableViewDataSource, UITableViewDelegate {}
And then all data sources should conform that protocol.
class MyDataSource: NSObject, GeneralDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return UITableViewCell()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
Then you can use it like this
var myDataSource: GeneralDataSource?
override func viewDidLoad() {
super.viewDidLoad()
self.myDataSource = MyDataSource()
self.tableView.delegate = self.myDataSource
}
Dynamic DataSource swap for UITableView in Swift
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.
How can I programmatically set dataSource of UITableView?
Change your code to:
class ViewController: UIViewController
{
@IBOutlet weak var table: UITableView!
var dataSource: MyData?
override func viewDidLoad()
{
super.viewDidLoad()
dataSource = MyData()
table.dataSource = dataSource!
}
}
Your app breaks because the ds
is deallocated as soon as viewDidLoad
returns. You have to keep a reference to your data source.
Dynamic UITableView Datasource inside of Static UITableView Cell
After some more experimentation I was able to figure it out! Looking back it seems obvious, but I was doing some weird circular referencing ridiculousness when I instantiated my datasource. I dropped out the init
function, and the reference to the tableView argument I was using in my dataSource subclass. This is what my dataSource class looks like now:
let dataSource = ValidatedCardsDataSource()
self.validatedCardsTable.dataSource = dataSource
self.validatedCardsTable.delegate = dataSource
class ValidatedCardsDataSource :NSObject, UITableViewDataSource, UITableViewDelegate {
var selectedRow = 0
let rowCount = 10
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100.0
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100.0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = "Title of Row: #\(indexPath.row)"
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print(indexPath.row)
self.selectedRow = indexPath.row
tableView.reloadData()
}
}
Now I just have to figure out my constraints... Thanks for the responses!
Dynamic UITableView in Swift
when user done with 1) got permission for the location and 2) filtered the closest shops these 2 things just call tableview.reloadData() and your tableview will be refresh & updated with latest data.
Set UITableView Delegate and DataSource
rickster is right. But I guess you need to use a strong
qualifier for your property since at the end of your viewDidLoad
method the object will be deallocated anyway.
@property (strong,nonatomic) SmallTable *delegate;
// inside viewDidload
[super viewDidLoad];
self.delegate = [[SmallTable alloc] init];
[self.myTable setDelegate:myTableDelegate];
[self.myTable setDataSource:myTableDelegate];
But is there any reason to use a separated object (data source and delegate) for your table? Why don't you set SmallViewController
as both the source and the delegate for your table?
In addition you are not creating the cell in the correct way. These lines do nothing:
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.textLabel.text = @"Hello there!";
dequeueReusableCellWithIdentifier
simply retrieves from the table "cache" a cell that has already created and that can be reused (this to avoid memory consumption) but you haven't created any.
Where are you doing alloc-init
? Do this instead:
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) {
cell = // alloc-init here
}
// Configure the cell...
cell.textLabel.text = @"Hello there!";
Furthermore say to numberOfSectionsInTableView
to return 1 instead of 0:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
Implementation of UITableView delegate and datasource in VIPER
Yes, data source and delegate are the parts of a view layer.
If you do not want your view to ask presenter for data, then you can do it like I describe it.
The data source class holds viewModels(dummy objects). You can then communicate via an interface. I mean you might understand better on some example:
protocol SomeViewProtocol {
func set(withVMS vms: [SomeViewModel])
}
final class SomeVC: SomeViewProtocol {
let dataSource: SomeDataSource
let tableView: UITableView
override func viewDidLoad() {
tableView.dataSource = dataSource
}
func set(withVMS vms: [SomeViewModel]) {
someDataSource.set(withVMS: vms)
tableView.reloadData()
}
}
protocol SomePresenterProtocol {
...
}
final class SomePresenter: SomePresenterProtocol {
fileprivate let view: SomeViewProtocol
//After view did load
func initAfterLoad() {
.
.
.
view.set(withVMS: viewModels)
}
}
But from my perspective, there is nothing wrong with View asking the presenter for the data.
Related Topics
Convert String to Nsdate in Swift
How to Customize the Font and Appearance of a Uialertcontroller in the New Xcode W/ iOS8
Display Table View When Searchbar (From Searchcontroller) Begin Edited Swift
Using Codable to Encode/Decode from Strings to Ints with a Function in Between
Moving Skspritenode to Location of the Touch
Type a Requires That Type B Be a Class Type Swift 4
Setting Nsunderlinestyle Causes Unrecogognized Selector Exception
How to Properly Check If Non-Optional Return Value Is Valid
How to Delete Item from Collection View
Swift 2: Invalid Conversion from Throwing Function of Type to Non-Throwing Function
How to Get Core Data Entity by It's Objectid
What's Wrong with My #If Target_Os_Simulator Code for Realm Path Definition
See Characters in an Nscharacterset (Swift)
I Can't Include ' Symbol to Regular Expressions