Separating Data Source to another class in Swift

血红的双手。 提交于 2019-11-27 11:48:50

问题


I'm trying to keep my view controllers clean as described in this article objc.io Issue #1 Lighter View Controllers. I tested this method in Objective-C and it works fine. I have a separate class which implements UITableViewDataSource methods.

#import "TableDataSource.h"

@interface TableDataSource()

@property (nonatomic, strong) NSArray *items;
@property (nonatomic, strong) NSString *cellIdentifier;

@end

@implementation TableDataSource

- (id)initWithItems:(NSArray *)items cellIdentifier:(NSString *)cellIdentifier {
    self = [super init];
    if (self) {
        self.items = items;
        self.cellIdentifier = cellIdentifier;
    }
    return self;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];

    cell.textLabel.text = self.items[indexPath.row];

    return cell;
}

@end

From the tableview controller, all I have to do is instantiate a instance of this class and set it as the tableview's data source and it works perfectly.

self.dataSource = [[TableDataSource alloc] initWithItems:@[@"One", @"Two", @"Three"] cellIdentifier:@"Cell"];
self.tableView.dataSource = self.dataSource;

Now I'm trying to do the same in Swift. First here's my code. Its pretty much of a translation of the Objective-C code above.

import Foundation
import UIKit

public class TableDataSource: NSObject, UITableViewDataSource {

    var items: [AnyObject]
    var cellIdentifier: String

    init(items: [AnyObject]!, cellIdentifier: String!) {
        self.items = items
        self.cellIdentifier = cellIdentifier

        super.init()
    }

    public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell

        cell.textLabel?.text = items[indexPath.row] as? String

        return cell
    }

}

And I call it like this.

let dataSource = TableDataSource(items: ["One", "Two", "Three"], cellIdentifier: "Cell")
tableView.dataSource = dataSource

But the app crashes with the following error.

-[NSConcreteNotification tableView:numberOfRowsInSection:]: unrecognized selector sent to instance

I checked the init method of TableDataSource and the items and the cell identifier gets passed fine. I had to declare the UITableViewDataSource methods public and remove the override keyword otherwise it would give compile time errors.

I'm clueless on what's going wrong here. Can anyone please help me out?

Thank you.


回答1:


Create a property for data source and use it with your tableview.

class ViewController: UIViewController {

  @IBOutlet weak var tableView: UITableView!
  var dataSource:TableDataSource!

  override func viewDidLoad() {
    super.viewDidLoad()

    dataSource = TableDataSource(items: ["One", "Two", "Three"], cellIdentifier: "Cell")

   tableView.dataSource = dataSource

  }
}



回答2:


I used the below code, for more generic approach, as a try..

import UIKit

class CustomDataSource<ItemsType, CellType:UITableViewCell>: NSObject, UITableViewDataSource {

    typealias ConfigureCellClosure = (_ item: ItemsType, _ cell: CellType) -> Void
    private var items: [ItemsType]
    private let identifier: String
    private var configureCellClosure: ConfigureCellClosure


    init(withData items: [ItemsType], andId identifier: String, withConfigBlock config:@escaping ConfigureCellClosure) {

        self.identifier = identifier
        self.items      = items
        self.configureCellClosure = config
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: self.identifier, for: indexPath) as! CellType
        configureCellClosure(items[indexPath.row], cell)
        return cell
    }

    func item(at indexpath: IndexPath) -> ItemsType {

        return items[indexpath.row]
    }

}

In view controller

   var dataSource: CustomDataSource<CellObject, CustomTableViewCell>?

    override func viewDidLoad() {
        super.viewDidLoad()

         dataSource = CustomDataSource<CellObject, CustomTableViewCell>(withData: customDataStore.customData(), andId: CustomTableViewCell.defaultReuseIdentifier) { (cellObject, cell) in
            cell.configureCell(with: cellObject)
        }
        customTableView.dataSource = dataSource

        // Do any additional setup after loading the view.
}

Used this approach in my small project WorldCountriesSwift




回答3:


Extending the accepted answer by "ayalcinkaya", which explains the how but not the why:

Most probably what is happening is that your TableDataSource is being deallocated as tableview.dataSource is a weak reference, that is why creating a property solves the problem, as it creates a strong reference and avoids the dataSource delegate being deallocated.



来源:https://stackoverflow.com/questions/25788782/separating-data-source-to-another-class-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!