7

我已经阅读了这个问题,并认为我理解这两种方法之间的区别,直到我找到一个奇怪的例子:

设置表格视图单元格的样式为Basic,Identifier 为Storyboard 中的Cell,代码如下:

import UIKit

class TableViewController: UITableViewController {
    var items: [String]!

    override func viewDidLoad() {
        super.viewDidLoad()
        items = ["first", "second", "third"]
    }

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        // either works fine
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell")! // let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)

        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
}

在此处输入图像描述

很简单,但是当我把tableView:cellForRowAtIndexPath:方法分别改成1、2、3、4种情况时:

情况1:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)

    cell.textLabel?.text = items[indexPath.row]
    return cell
}

案例二:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier("Cell")!

    cell.textLabel?.text = items[indexPath.row]
    return cell
}

案例3:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
    cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)

    cell.textLabel?.text = items[indexPath.row]
    return cell
}

案例4:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
    cell = tableView.dequeueReusableCellWithIdentifier("Cell")!

    cell.textLabel?.text = items[indexPath.row]
    return cell
}

案例 1、2(不起作用):

在此处输入图像描述

案例 3、4(工作正常):

在此处输入图像描述

如何解释?我认为从另一个角度理解这两种方法确实很有帮助,欢迎任何意见。

4

2 回答 2

6

在每种情况下,您要为每行出列两个单元格。在情况 1 和 2 中,您首先调用("Cell", forIndexPath: indexPath)版本。在这种情况下,表格视图以每行两个单元格结束,一个完全重叠并遮住另一个。您可以在视图检查器中看到这一点,因为您可以修改视角以查看后面:

在此处输入图像描述

(我修改了cellForRowAtIndexPath这样的代码:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("plainCell", forIndexPath: indexPath)
    cell.textLabel!.text = "First cell for row \(indexPath.row)"
    cell = tableView.dequeueReusableCellWithIdentifier("plainCell", forIndexPath: indexPath)
    cell.textLabel!.text = "Second cell for row \(indexPath.row)"
    print("Cell being returned is \(cell)")
    return cell
}

为每个单元格提供不同的文本标签。)在案例 3 和 4 中,您("Cell")首先调用版本,表格视图的每一行只有一个单元格。

为什么会有不同的行为?如果您创建自定义子类UITableViewCell并在情节提要中使用它,则可以覆盖各种方法并添加print()语句以查看发生了什么。特别是awakeFromNibdidMoveToSuperViewdeinit。发生的是,在案例 1 和 2 中,第一个单元格被创建(awakeFromNib)并立即添加(didMoveToSuperView)到超级视图,大概是表视图或其子视图之一。在案例 3 和 4 中,创建了第一个单元格,但未将其添加到超级视图中。相反,一段时间后,单元被释放(deinit)。

(请注意,如果第二个单元格使用("Cell", forIndexPath: indexPath)版本出列,它也会立即添加到超级视图中。但是,如果第二个单元格使用("Cell")版本出列,则仅在方法返回后才将cellForRowAtIndexPath其添加到超级视图中。)

所以关键的区别在于("Cell", forIndexPath: indexPath)版本导致单元格被立即添加到表格视图中,甚至在cellForRowAtIndexPath完成之前。您提到的问题/答案中暗示了这一点,因为它表明出队的单元格的大小将正确。

一旦添加到超级视图,第一个单元格就不能被释放,因为它的超级视图仍然有一个强引用。如果单元格与("Cell")版本一起出队,它们不会被添加到超级视图中,因此一旦cell变量被重新分配,就没有对它们的强引用,因此它们被释放。

希望这一切都有意义。

于 2016-03-15T00:48:21.823 回答
2

dequeueReusableCellWithIdentifier:不给你保证:单元格可能是nil,所以你必须检查你的单元格是否nil正确并正确处理它,否则你的应用程序将崩溃。

dequeueReusableCellWithIdentifier:forIndexPath:,另一方面,它会为你检查这个(它总是返回一个单元格)。

对于您的特定情况(Swift),这意味着您可以安全地使用 强制解开单元格dequeueReusableCellWithIdentifier:forIndexPath:,而您必须使用if let第二种语法。

示例代码(在 Objective-C 中,我不使用 Swift)

dequeueReusableCellWithIdentifier:forIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" atIndexPath:indexPath];

    // Here we know the cell is not nil (....atIndexPath: ensures it)
    cell.textLabel.text = items[indexPath.row];

    return cell;
}

dequeueReusableCellWithIdentifier:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

    // You asked for a cell, but you don't know if it is nil or not
    // In Swift, here the cell should be a conditional

    // First, check if the cell is nil
    if ( cell == nil ) {
        // Cell is nil. To avoid crashes, we instantiate an actual cell
        // With Swift conditional should be something similar
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }

    // Here you're sure the cell is not nil
    // If condicional, you probably will write cell?.textLabel?.text = items[indexPath.row];
    cell.textLabel.text = items[indexPath.row];

    // Finally, you return the cell which you're 100% sure it's not nil
    return cell;
}
于 2016-03-14T10:52:15.647 回答