Swift中的UITableView

61

我正在努力找出这段代码有什么问题。在Objective-C中它目前可以工作,但在Swift中它在方法的第一行就会崩溃。控制台日志显示一个错误消息:Bad_Instruction

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

        if (cell == nil) {
            cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "Cell")
        }

        cell.textLabel.text = "TEXT"
        cell.detailTextLabel.text = "DETAIL TEXT"

        return cell
    }

自定义单元格在Swift中,这可能会帮助你... [https://dev59.com/nmAf5IYBdhLWcg3w52A_#30475620] - Mohamed Jaleel Nazir
17个回答

82

还请参考matt的答案,其中包含解决方案的后半部分。

让我们找到一种在不创建自定义子类或nibs的情况下解决问题的方法。

实际问题在于Swift区分可以为空(nil)和不能空的对象。如果没有为标识符注册nib,则dequeueReusableCellWithIdentifier可能返回nil

这意味着我们必须将变量声明为可选项:

var cell : UITableViewCell?

我们必须使用 as? 进行强制类型转换,而不是使用 as

//variable type is inferred
var cell = tableView.dequeueReusableCellWithIdentifier("CELL") as? UITableViewCell

if cell == nil {
    cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "CELL")
}

// we know that cell is not empty now so we use ! to force unwrapping but you could also define cell as
// let cell = (tableView.dequeue... as? UITableViewCell) ?? UITableViewCell(style: ...)

cell!.textLabel.text = "Baking Soda"
cell!.detailTextLabel.text = "1/2 cup"

cell!.textLabel.text = "Hello World"

return cell

1
谢谢您的回答!我一直在努力理解Swift中的“可选项”,因为您是正确的,如果没有自定义,创建一个表视图单元格子类是毫无意义的。我一定会尝试这个! - DBoyer
@Sulthan 我使用和你完全一样的代码,但是在dequeueReusableCellWithIdentifier这行出现了EXC_BAD_INSTRUCTION错误。你有什么想法吗? - user470763
在尝试了许多SO帖子后,我发现当我像上面那样使用cell != nil时,它可以解决类似的问题。为什么会这样呢? - iamprem
1
这个方法期望返回一个 UITableViewCell。不是可选类型。那么当你返回时,你是否应该强制解包它? - Daniel Wood
在Swift 3中显示警告:从'UITableViewCell?'到'UITableViewCell'的条件下转换没有任何作用。 - Abdul Hannan
显示剩余3条评论

64
Sulthan的回答很聪明,但真正的解决方案是:不要调用dequeueReusableCellWithIdentifier。那就是你一开始的错误。
这个方法已经过时了,我很惊讶它尚未被正式弃用;任何可以容纳Swift(iOS 7或iOS 8)的系统都不需要它来做任何事情。
相反,调用现代方法dequeueReusableCellWithIdentifier:forIndexPath:。这有一个优点,即没有涉及到可选项;保证会返回单元格。所有的问号和感叹号都消失了,你可以使用let而不是var,因为单元格的存在是有保证的,你生活在一个方便、现代的世界中。
如果你没有使用故事板,你必须提前为此标识符注册表格,注册类或nib。通常要这样做的地方是viewDidLoad,这是表视图存在的最早时间。
下面是使用自定义单元格类的示例:
override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.registerClass(MyCell.self, forCellReuseIdentifier: "Cell")
}

// ...

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath) as MyCell
    // no "if" - the cell is guaranteed to exist
    // ... do stuff to the cell here ...
    cell.textLabel.text = // ... whatever
    // ...
    return cell
}

但是如果你正在使用故事板(大多数人都这样做),甚至不需要在 viewDidLoad 中注册表视图!只需在故事板中输入单元格标识符,然后使用 dequeueReusableCellWithIdentifier:forIndexPath: 就可以开始工作了。


3
如果可重用的单元格被称为"Cell",那么在你的示例中,"MyCell"是从哪里来的? - JoshL
@JoshL,你的问题毫无意义。单元格重用标识符和单元格类别之间没有任何关系。 - matt
这应该被标记为正确答案。@Sulthan的答案是我之前所在的地方,努力将返回可选项的dequeue方法塞进返回非可选项的数据源方法中。这个解决方案让所有这些问题都消失了。 - Echelon

18

@Sulthan的答案完全正确。一个可能方便的修改是将单元格强制转换为UITableViewCell!,而不是UITableViewCell。

func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    var cell = tableView.dequeueReusableCellWithIdentifier("CELL") as UITableViewCell!
    if !cell {
        cell = UITableViewCell(style:.Default, reuseIdentifier: "CELL")
    }
    // setup cell without force unwrapping it
    cell.textLabel.text = "Swift"
    return cell
}

现在,你可以修改cell变量而不需要每次强制解包。使用隐式解包可选项时要小心。你必须确信你正在访问的值有一个值。

要了解更多信息,请参阅《Swift编程语言》中的“隐式解包可选项”部分。


8

试试这个:

func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
    cell.textLabel.text = "\(indexPath.row)"

    return cell
}

请注意,在创建UITableView实例时,您应该注册您的UITableViewCell和ID:
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "Cell")

1
这个方法可行,肯定会让我更接近目标,但是这需要你注册一个单元格,因为如果使用“let”你不能对单元格进行赋值,而注册单元格会阻止使用CellStyleValue1的能力。 - DBoyer
2
如果您想要更改单元格样式,只需创建一个自定义单元格子类并在init方法中使用所需的样式即可。 - CW0007007
1
对我来说,不同的单元格样式对应着不同的行为,因此创建一个自定义的 UITableViewCell 子类更有意义。 - Oscar Swanros
2
好的,我明白了。你是对的,它完美地运行了。幸运的是,Swift让向同一文件中添加另一个子类变得更容易了。 - DBoyer
3
是的!您不再需要导入任何自定义类了!太美了 :-) - Oscar Swanros
显示剩余2条评论

8

以下是我编写的使其正常工作的代码...

首先,将表格视图单元格注册到表格视图中。

self.tableView.registerClass(MyTableViewCell.self, forCellReuseIdentifier: "Cell")

然后配置cellForRowAtIndexPath。
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!  {
    var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as MyTableViewCell

    cell.textLabel.text = "Cell Text"
    cell.detailTextLabel.text = "Cell Detail Text in Value 1 Style"

    return cell
}

接着我定义了一个自定义的单元格子类,写在文件底部(因为现在这样做更容易)

class MyTableViewCell : UITableViewCell {

    init(style: UITableViewCellStyle, reuseIdentifier: String!) {
        super.init(style: UITableViewCellStyle.Value1, reuseIdentifier: reuseIdentifier)
    }

}

6
这里有一个简单的方法来定义 swift 2 中的表格单元格:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let identifier = "cell"
    let cell = tableView.dequeueReusableCellWithIdentifier(identifier) ??
        UITableViewCell.init(style: UITableViewCellStyle.Default, reuseIdentifier: identifier)
    cell.textLabel!.text = "my text"
    return cell
}

Swift 3:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let identifier = "cell"
    let cell = tableView.dequeueReusableCell(withIdentifier: identifier) ??
        UITableViewCell(style: .default, reuseIdentifier: identifier)
    cell.textLabel!.text = "my text"
    return cell
}

4

这里有几个答案,但我认为它们都不理想,因为在声明后,你最终得到一个可选的UITableViewCell,然后在任何声明中都需要使用cell!...。 我认为这是更好的方法(我可以确认这可以在Xcode 6.1上编译):

var cell:UITableViewCell

if let c = tableView.dequeueReusableCellWithIdentifier("cell") as? UITableViewCell {
    cell = c
}
else {
    cell = UITableViewCell()
}

2

我已经以如下方式完成了这个任务:

Swift中使用UITableView的步骤:

  • ViewController中添加UITableView
  • ViewController.swift类中赋予Referencing Outlets
  • dataSourcedelegate Outlets赋予给ViewController

现在在ViewController.swift类中,Swift代码如下:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var mTableView: UITableView!

    var items: [String] = ["Item 1","Item 2","Item 3", "Item 4", "Item 5"]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.mTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

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

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

        cell.textLabel?.text = self.items[indexPath.row]
         println(self.items[indexPath.row])

        return cell
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        println("You have selected cell #\(indexPath.row)!")
    }
}

现在是时候运行你的程序了。 完成

我在每个函数中都遇到了一个错误,提示我在“func”之前没有使用“override”。我猜是因为我正在使用UITableViewController而不是UITableViewDelegate。 - Randy L
@the0ther,是的。你能按照我的步骤来做吗? - Hiren Patel

1
使用 "as" 关键字将执行以下两个步骤:
1. 创建一个可选值,其中包装了 UITableViewCell 的变量;
2. 解包可选值。

因此,通过这样做

var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Component") as UITableViewCell

您将得到一个“普通”的UITableViewCell类型的变量:cell。理论上讲,这样做是可以的。但下一行。
if (cell == nil) {}

造成了麻烦,因为在Swift中,只有可选值才能被赋予nil。
所以,为了解决这个问题,你必须将cell定义为Optional类型的变量。就像这样:
var cell = tableView.dequeueReusableCellWithIdentifier("Component") as? UITableViewCell

使用关键字"as?"将创建一个可选变量,这无疑可以被赋值为nil。

1
实际上,在苹果的 TableView 指南文档和示例代码中,您会找到以下句子: 如果 dequeueReusableCellWithIdentifier: 方法请求一个在 storyboard 中定义的单元格,则该方法始终返回有效的单元格。 如果没有可回收的单元格等待重用,则该方法使用故事板本身中的信息创建一个新单元格。 这消除了检查 nil 的返回值并手动创建单元格的需要。 因此,我们只需编写以下代码:
var identifer: String = "myCell"
var cell = tableView.dequeueReusableCellWithIdentifier(identifer) as UITableViewCell
cell.textLabel.text = a[indexPath.row].name
cell.detailTextLabel.text = "detail"

我认为这是使用 tableView 的合适方式


这是正确的,但是除非您指定单元格样式为“subtitle”,否则永远不会显示detailLabel。那么,如何告诉Xcode使用tableView.dequeueReusableCellWithIdentifiersubtitle样式呢? - kakubei

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