在Swift中将数据源分离到另一个类中

21

我试图像这篇文章描述的那样保持我的视图控制器干净 objc.io Issue #1 Lighter View Controllers。我在Objective-C中测试了这种方法,它可以正常工作。我有一个单独的类来实现UITableViewDataSource方法。

#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

从tableview控制器来看,我所要做的就是实例化这个类的一个实例并将其设置为tableview的数据源,这样它就可以完美地工作了。

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

现在我正在尝试用Swift做同样的事情。首先是我的代码。它基本上是以上Objective-C代码的翻译。

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
    }

}

我就这样称呼它。

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

但该应用程序崩溃,并显示以下错误:

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

我检查了TableDataSourceinit方法,项目和单元格标识符都被正确传递。我不得不将UITableViewDataSource方法声明为public并删除override关键字,否则它会在编译时出错。

我对发生了什么问题感到困惑。请问有人可以帮帮我吗?

谢谢。


你能显示出崩溃点处的堆栈跟踪吗? - Mike Pollard
2
看起来你的数据源没有被保留。你把引用存储在哪里了? - jlehr
3
那确实是问题所在。我之前并没有储存它!我创建了一个属性 var dataSource: TableDataSource! 并将其赋值给tableview的 dataSource 属性,现在它可以工作了 :) 谢谢。 - Isuru
5
这是因为在Swift中,UITableView(以及其他视图/控件)的delegatedataSource属性被声明为unowned,所以如果它不是视图控制器本身,你需要自己引用委托/数据源。 - Nate Cook
@NateCook 我确实在检查 dataSource 属性的类型时看到了 unowned,但不知道它是什么。谢谢你澄清了这一点。 - Isuru
你如何使用新项目更新数据源?是否有任何方法可以更新表格列表? - iPhone Guy
3个回答

28
创建一个数据源属性,并在您的表格视图中使用它。
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
请问您能否将TableDataSource类粘贴在这里? - Qadir Hussain

7

在 "ayalcinkaya" 回答的基础上进行扩展,该回答解释了如何做到,但未解释为什么要这样做:

很可能发生的情况是您的TableDataSource被释放了,因为tableview.dataSource是一个弱引用,这就是为什么创建一个属性可以解决问题的原因,因为它创建了一个强引用并避免了dataSource委托被释放。


6
我使用了以下代码,为了更通用的方法,请试一试...
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]
    }

}

在视图控制器中

   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.
}

我在我的小项目WorldCountriesSwift中采用了这种方法。

我将DataSource放在了单独的文件中,但不知道如何“监听点击事件并将所点击的项目发送到DetailViewController”。 }``` 在PicturesDataSource内,“storyboard”和“navigationController”不可用,因此```if let detailViewController = storyboard.instantiateViewController(withIdentifier: "PictureDetailView") as? PictureDetailViewController & navigationController?.pushViewController(detailViewController, animated: true)```无法工作。 - MD TAREQ HASSAN
尝试在视图控制器的didselectrow方法中添加dataSource?.item(at: indexPath),然后尝试呈现详细信息pictureVC.present(PictureDetailViewController.....)。 - anoop4real

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接