如何在UITableViewContoller / UITableViewCell

时间:2016-09-06 23:16:32

标签: ios swift ios9

关于代码已经改变了,因为我在过去一周左右一直试图调试这个 - 因此,我已将原始帖子移动到PDF并开始主要是新鲜的:

简短概要基本不变

  • 包含的UITableViewController(包含自定义类) UITableViewCell的(也是自定义类)
  • 表定义为仅允许单个选择
  • 初始“空”单元格(row = 0)设置为在顶部提供一些填充 视图
  • 希望能够切换选择的单元格(单击一次, 选择;再次点击它,取消选中)
  • 代码我看起来(对我来说)它应该工作,但不是
  • 所以,寻找一些指导/帮助等等。

代码

以下是[自定义UITableViewController代码的相关部分(略微重新格式化)

import UIKit

class ExistingLocationsViewController: UITableViewController {
    var oldRow = -9999
    var newRow = -1
    var locationList: [LocationObject] = [
        LocationObject(name: "name-1", address: "addr-1", phone: "phone-1", latitude: 40.0, longitude: -80.1),
        LocationObject(name: "name-2", address: "addr=2", phone: "phone-2", latitude: 40.0, longitude: -80.1)
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        // provides buffer space at the top so that the contents do not run into the top-margin (not needed if first cell is [intnetionally] blank)
        self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
    }

    //... didReceiveMemoryWarning, numberOfSectionsInTableView, tableView(tableView: UITableView, numberOfRowsInSection section: Int)
    // Did an override of willDisplayCell, but it seemed to neither hurt nor help my issue, so I removed it

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("resultCell", forIndexPath: indexPath) as! ExistingLocationTableViewCell
        let row  = indexPath.row
        let item = locationList[row]

        cell.nameLabel.text     = item.name
        cell.locationLabel.text = item.address

        print("cellForRowAtIndexPath: (row = \(row))(oldRow = \(oldRow))(newRow = \(newRow))[cell.selected = \(cell.selected)]")
        //...attempt to set cell.accessoryType here neither hurt nor helped my issue, so I removed them

        return cell
    }

    override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
        let cell = tableView.dequeueReusableCellWithIdentifier("resultCell", forIndexPath: indexPath) as! ExistingLocationTableViewCell
        print("didDeselectRowAtIndexPath: (row = \(indexPath.row))(oldRow = \(oldRow))(newRow = \(newRow))[cell.selected = \(cell.selected)]")
        print("didDeselectRowAtIndexPath => reloadRowsAtIndexPaths")
        self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
    }

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let cell = tableView.dequeueReusableCellWithIdentifier("resultCell", forIndexPath: indexPath) as! ExistingLocationTableViewCell
        newRow = indexPath.row
        print("didSelectRowAtIndexPath: (row = \(indexPath.row))(oldRow = \(oldRow))(newRow = \(newRow))[cell.selected = \(cell.selected)]")
        if newRow == oldRow  {
            print("didSelectRowAtIndexPath => didDeselectRowAtIndexPath")
            self.tableView(tableView, didDeselectRowAtIndexPath: indexPath)
            cell.setSelected(false, animated: false) // before or after call to didDeselect but must be *here* and not *there*
            oldRow = -1
        }
        else {
            print("didSelectRowAtIndexPath => reloadRowsAtIndexPaths")
            self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
            cell.setSelected(true, animated: false) // must be *after* call to reloadRowsAtIndexPaths
            oldRow = indexPath.row
        }
        print("didSelectRowAtIndexPath: oldRow = '\(oldRow)'")
    }

    //... canEditRowAtIndexPath,  commitEditingStyle
}

以下是自定义UITableViewCell代码(...)

import UIKit

class ExistingLocationTableViewCell: UITableViewCell {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var locationLabel: UILabel!

    override func awakeFromNib() { super.awakeFromNib() }

    // This is where the checkmark happens
    override func setSelected(selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        let nm = self.nameLabel.text
        let ad = self.locationLabel.text
        if (nm?.isEmpty == false) { //avoids many calls to this function which don't represent an actual row
            print("setSelected(\(selected), \(animated)): [name='\(nm)', location='\(ad)']:")
            self.accessoryType = (selected == true) ? .Checkmark : .None
        }
    }

}

记录

下面是日志输出和我所看到的图像(带有一些额外的评论)

(1)初始显示

    cellForRowAtIndexPath: (row = 0)(oldRow = -9999)(newRow = -1)[cell.selected = false]
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    cellForRowAtIndexPath: (row = 1)(oldRow = -9999)(newRow = -1)[cell.selected = false]
    setSelected(false, false): [name='Optional("name-2")', location='Optional("addr-2")']:
    Initial Display
    GOOD:没有检查也没有突出显示


(2)CLICK-1:TOGGLE ON
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: (row = 0)(oldRow = -9999)(newRow = 0)[cell.selected = true]
    didSelectRowAtIndexPath => reloadRowsAtIndexPaths 
    cellForRowAtIndexPath: (row = 0)(oldRow = -9999)(newRow = 0)[cell.selected = false]
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: oldRow = '0' 
    Initial click
    GOOD:第1行检查并突出显示


(3)CLICK-2:TOGGLE OFF
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: (row = 0)(oldRow = 0)(newRow = 0)[cell.selected = true]
    didSelectRowAtIndexPath => didDeselectRowAtIndexPath 
    didDeselectRowAtIndexPath: (row = 0)(oldRow = 0)(newRow = 0)[cell.selected = true]
    didDeselectRowAtIndexPath => reloadRowsAtIndexPaths 
    cellForRowAtIndexPath: (row = 0)(oldRow = 0)(newRow = 0)[cell.selected = false]
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: oldRow = '-1' 
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    Second click
    GOOD:第1行未选中且未突出显示
setSelected的一些额外调用似乎发生,不确定原因。
(4)CLICK-3:TOGGLE ON?
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: (row = 0)(oldRow = -1)(newRow = 0)[cell.selected = true]
    didSelectRowAtIndexPath => reloadRowsAtIndexPaths 
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    cellForRowAtIndexPath: (row = 0)(oldRow = -1)(newRow = 0)[cell.selected = false]
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: oldRow = '0' 
    BAD:第1行仍未检查且未突出显示
    无论您点击同一行多少次,图像仍然与上面完全相同) (如果你点击第二行,它会在第一次出现工作的同一周期,然后在第二次点击后再显示为选中
应该看起来与行上的第一次点击相同,但是有额外的setSelected来电并且最终结果是错误的。

(5)CLICK-4:TOGGLE OFF?
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(true, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: (row = 0)(oldRow = 0)(newRow = 0)[cell.selected = true]
    didSelectRowAtIndexPath => didDeselectRowAtIndexPath 
    didDeselectRowAtIndexPath: (row = 0)(oldRow = 0)(newRow = 0)[cell.selected = true]
    didDeselectRowAtIndexPath => reloadRowsAtIndexPaths 
    cellForRowAtIndexPath: (row = 0)(oldRow = 0)(newRow = 0)[cell.selected = false]
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    didSelectRowAtIndexPath: oldRow = '-1' 
    setSelected(false, false): [name='Optional("name-1")', location='Optional("addr-1")']:
    OKAY:第1行未选中且未突出显示,但可能不是正确的原因

所以... 再次,我问 - 你看到我做错了吗?我还应该做些什么来调试这个?

3 个答案:

答案 0 :(得分:1)

你发布了很多很难理解的内容。然而,一个潜在的问题突出。我认为您需要使用复选标记附件来指示单元格已被选中,并且您具有以下代码:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("resultCell", forIndexPath: indexPath) as! ExistingLocationTableViewCell
    let row  = indexPath.row
    let item = locationList[row]

    cell.nameLabel.text     = item.name
    cell.locationLabel.text = item.address
    cell.accessoryType      = .None // initialize accessory icon?

    return cell
}

然后在didSelectdidDeselect您正在更改配件。这不是UITableView的工作原理。它不记得状态 - 你必须自己做。在didSelect内,您应该存储indexPath,然后重新加载表。这将通过再次调用cellForRowAtIndexPath重绘所有可见单元格。那里的代码应该决定是否使用类似于:

的代码显示复选标记
cell.accessoryType = (indexPath == mySelectedIndexPath) ? .Checkmark : .None

mySelectedIndexPath是您的存储状态。这应该在viewController中定义,而不是在单元格中定义 - 当您调用dequeueReusableCellWithIdentifier时,您可能会获得一个新单元格,或者现有已初始化的单元格已移出屏幕外。每次获得一个,你必须假设它包含另一行的数据,并适当地设置一切。

答案 1 :(得分:0)

这不是记录单元格中所选状态的好方法。 您可以在UITableViewController中记录选定的索引路径,在didSelectRowAtIndexPath方法中,您可以查看它是否已被选中,以及您希望这个索引路径。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("resultCell", forIndexPath: indexPath) as! ExistingLocationTableViewCell

    cell.nameLabel.text     = item.name
    cell.locationLabel.text = item.address

    if indexPath.row == selectedIndexPath { 

        //this cell is selected
        cell.nameLabel.textColor = [UIColor redColor];
    }
    else {
        //this cell is not selected
        cell.nameLabel.textColor = [UIColor whiteColor];
    }

    return cell
}


override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let cell = tableView.dequeueReusableCellWithIdentifier("resultCell", forIndexPath: indexPath) as! ExistingLocationTableViewCell
    if indexPath.row == selectedIndexPath { // ignore the initial blank row
        //do something like deselect
    }
    else {
        //do something like select
    }
}

答案 2 :(得分:0)

在另一个名单的人(Ole Begemann)的帮助下,我能够大大清理我的代码并使其完全正常工作。似乎没有按照要求工作的一件事是所选行保持突出显示而不是突出显示逐渐消失。

新代码涉及所有三个文件:

<强> LocationObject.swift
添加了一个字段,用于跟踪每组数据的选择状态:     //将在其他地方使用的对象的简单类可能会被像UIMapKit这样的东西替换,但是现在......     进口基金会

class LocationObject {
    var name:      String!
    var address:   String!
    var phone:     String!
    var latitude:  Double!
    var longitude: Double!
    var selected:  Bool!   //<<== Added

    init(name: String, address: String, phone: String, latitude: Double, longitude: Double, selected: Bool) { //<<== Added
        self.name      = name
        self.address   = address
        self.phone     = phone
        self.latitude  = latitude
        self.longitude = longitude
        self.selected  = false     //<<== Added
    }
}

<强> ExistingLocationTableViewCell.swift
不清楚是否甚至需要这个,因为我基本上删除了我之前做的自定义,但保留它以防万一它我需要在以后覆盖或扩展一些东西。     导入UIKit

class ExistingLocationTableViewCell: UITableViewCell {
    @IBOutlet weak var nameLabel:     UILabel!
    @IBOutlet weak var locationLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    // Removed override of setSelected() - didn't seem to do anything
}

<强> ExistingLocationsViewController
这是主要部分,涉及许多变化和清理。     导入UIKit

class ExistingLocationsViewController: UITableViewController {
    var oldRow = -1 // Still used for aspect of state information (-1 instead of -9999)
    //newRow removed, wasn't needed
    var locationList: [LocationObject] = [ //<< Added 3rd row of data for more testing
        LocationObject(name: "name-1", address: "addr-1", phone: "phone-1", latitude: 40.0, longitude: -80.1, selected: false),
        LocationObject(name: "name-2", address: "addr-2", phone: "phone-2", latitude: 40.0, longitude: -80.1, selected: false),
        LocationObject(name: "name-3", address: "addr-3", phone: "phone-3", latitude: 40.0, longitude: -80.1, selected: false)
    ]

上面没有任何重大变化。

    override func viewDidLoad() {
        super.viewDidLoad()
        // provides buffer space at the top so that the contents do not run into the top-margin (not needed if first cell is [intnetionally] blank)
        self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
    }

上面没有任何改变。 为简洁起见,未显示以下内容,因为其中没有任何重要内容:

  • didReceiveMemoryWarning
  • numberOfSectionsInTableView
  • numberOfRowsInSection

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("resultCell", forIndexPath: indexPath) as! ExistingLocationTableViewCell

        let row  = indexPath.row
        let item = locationList[row]

        cell.nameLabel.text     = item.name
        cell.locationLabel.text = item.address

        return cell
    }

基本未更改,删除了调试语句

    override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
        let cell                   = tableView.cellForRowAtIndexPath(indexPath) as! ExistingLocationTableViewCell
        let row                    = indexPath.row
        cell.accessoryType         = .None
        locationList[row].selected = false
        oldRow                     = -1
    }

以上内容发生了显着变化:

  • 使用cellForRowAtIndexPath代替dequeueReusableCellWithIdentifier
  • 将cell.accessoryType明确设置为&# 39; .None&#39;
  • 明确将LocationObject中的selected字段设置为&#39; false&#39;
  • 重置&#39; oldRow&#39;到&#39; -1&#39;因为此功能仅在取消选择不同的以前选择的行时被调用(即:不是相同行的切换)

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let cell                   = tableView.cellForRowAtIndexPath(indexPath) as! ExistingLocationTableViewCell
        let row                    = indexPath.row
        locationList[row].selected = !locationList[row].selected
        cell.accessoryType         = (locationList[row].selected == true) ? .Checkmark : .None
        cell.highlighted           = false
        cell.setHighlighted(false, animated: true)

        if oldRow == row { // TOGGLE
            let oldIndexPath = NSIndexPath(forRow: oldRow, inSection: 0)
            tableView.deselectRowAtIndexPath(oldIndexPath, animated: true)
            oldRow = -1
        }
        else {
            oldRow = row
        }
    }
}

以上内容发生了显着变化:

  • 使用cellForRowAtIndexPath代替dequeueReusableCellWithIdentifier
  • 将LocationObject中的selected字段设置为它正好相反(false =&gt; true || true =&gt; false
  • 使用该值设置{ {1}}
  • 如果之前选择了相同的行,请取消选中它(关闭突出显示)
  • cell.accessoryType状态信息设置为引用当前选定的行(已选择)或&#39; oldRow&#39; (未选)

    上述更改的组合解决了我试图让行选择在辅助点击上切换的问题。希望这对于下一个尝试这样做的人来说会派上用场......