0

我在表格视图中显示密码,当我选择一个单元格时,它应该选择,如果我再次点击同一个单元格,那么它应该取消选择(而点击单元格应该像开关一样工作)

在此处输入图像描述

但使用以下代码

问题1:最初我无法选择第一行,但在选择任何其他行之后,然后能够选择第一行..为什么?我哪里错了?

问题2:如果我连续点击第三次然后无法选择同一行,我只能选择一次取消选择同一行两次点击,然后无法选择同一行,为什么?..请指导

class PincodeModel{
var name: String?
var id: Int?
var isSelected: Bool

init(name: String?, id: Int?, isSelected: Bool) {
    self.name = name
    self.id = id
    self.isSelected = isSelected
}
}


class FilterViewController: UIViewController {

var pincodePreviousIndex: Int = -1
var pincodes = [PincodeModel]()

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    for pincode in pincodeList {
        self.pincodes.append(PincodeModel(name: pincode, id: 0, isSelected: false))
    }
}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "SubFilterTableViewCell", for: indexPath) as! SubFilterTableViewCell
        cell.title.text = self.pincodes[indexPath.row].name

        if !self.pincodes.isEmpty {
            if self.pincodes[indexPath.row].isSelected == true {
                cell.tickImageView.image =  #imageLiteral(resourceName: "iconTick")
            }else {
                cell.tickImageView.image = UIImage()
            }
        }
    return cell
}

 // EDITED Code according to below answer
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
   
    self.pincodes[indexPath.row].isSelected = !self.pincodes[indexPath.row].isSelected

if self.pincodes[indexPath.row].isSelected == true {
self.filterData.pincode = pincodes[indexPath.row].name ?? ""
}else {
self.filterData.pincode = ""
}
if pincodePreviousIndex > 0 && pincodePreviousIndex != indexPath.row {
pincodes[pincodePreviousIndex].isSelected = false
}
pincodePreviousIndex = indexPath.row

}

当我从索引 = 1 中选择时,这是我想要的工作,但是如果我选择第一行(索引 = 0),那么如果我选择另一行,那么正确的标记仍然存在,为什么?

o/p 与编辑的代码:

在此处输入图像描述

4

2 回答 2

1

您可以采取几种方法来为自己省去一些麻烦。

首先,.selectionStyle = .none在您的单元格上设置,然后在您的单元格类中覆盖setSelected(...). 例如,我向我的单元格添加了一个图像视图,并给它一个空框作为其图像,并给它一个选中框作为其突出显示的图像:

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    imgView.isHighlighted = selected ? true : false
}

现在单元格外观将反映其由表格视图维护的选定状态。

接下来,代替didSelectRowAt,我们将实现willSelectRowAt... 如果当前选择了单元格,我们将取消选择它(并更新我们的数据):

func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
    // is a row already selected?
    if let idx = tableView.indexPathForSelectedRow {
        if idx == indexPath {
            // tapped row is already selected, so
            //  deselect it
            tableView.deselectRow(at: indexPath, animated: false)
            //  update our data
            pincodes[indexPath.row].isSelected = false
            //  tell table view NOT to select the row
            return nil
        } else {
            // some other row is selected, so
            //  update our data
            //  table view will automatically deselect that row
            pincodes[idx.row].isSelected = false
        }
    }
    // tapped row should now be selected, so
    //  update our data
    pincodes[indexPath.row].isSelected = true
    //  tell table view TO select the row
    return indexPath
}

这是一个完整的例子:

class PincodeModel{
    var name: String?
    var id: Int?
    var isSelected: Bool
    
    init(name: String?, id: Int?, isSelected: Bool) {
        self.name = name
        self.id = id
        self.isSelected = isSelected
    }
}

class SelMethodTableViewController: UIViewController {

    var pincodes: [PincodeModel] = []
    
    let tableView = UITableView()
    
    let infoView: UIView = {
        let v = UILabel()
        v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        return v
    }()
    let infoTitle: UILabel = {
        let v = UILabel()
        v.text = "Info:"
        return v
    }()
    let infoLabel: UILabel = {
        let v = UILabel()
        v.numberOfLines = 0
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        for i in 0..<20 {
            let pcm = PincodeModel(name: "\(i)", id: i, isSelected: false)
            pincodes.append(pcm)
        }

        [tableView, infoView, infoTitle, infoLabel].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        [infoTitle, infoLabel].forEach { v in
            infoView.addSubview(v)
        }
        [tableView, infoView].forEach { v in
            view.addSubview(v)
        }
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain the table view on right-side of view
            tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            tableView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.5),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            tableView.bottomAnchor.constraint(equalTo: infoView.topAnchor, constant: -16.0),

            // let's add a tappable "info" view below the table view
            infoView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            infoView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            infoView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
            infoView.heightAnchor.constraint(equalToConstant: 120.0),
            
            // add labels to infoView
            infoTitle.topAnchor.constraint(equalTo: infoView.topAnchor, constant: 8.0),
            infoTitle.leadingAnchor.constraint(equalTo: infoView.leadingAnchor, constant: 8.0),
            infoTitle.trailingAnchor.constraint(equalTo: infoView.trailingAnchor, constant: -8.0),
            infoLabel.topAnchor.constraint(equalTo: infoTitle.bottomAnchor, constant: 8.0),
            infoLabel.leadingAnchor.constraint(equalTo: infoView.leadingAnchor, constant: 8.0),
            infoLabel.trailingAnchor.constraint(equalTo: infoView.trailingAnchor, constant: -8.0),
            //infoLabel.bottomAnchor.constraint(lessThanOrEqualTo: infoView.bottomAnchor, constant: -8.0),
            
        ])

        tableView.dataSource = self
        tableView.delegate = self
        
        tableView.register(MyToggleCell.self, forCellReuseIdentifier: "toggleCell")
        
        // just so we can see the frame of the table view
        tableView.layer.borderWidth = 1.0
        tableView.layer.borderColor = UIColor.red.cgColor

        let t = UITapGestureRecognizer(target: self, action: #selector(showInfo(_:)))
        infoView.addGestureRecognizer(t)
        infoView.isUserInteractionEnabled = true
    }

    @objc func showInfo(_ g: UIGestureRecognizer) -> Void {
        var s: String = ""
        
        let selectedFromData = pincodes.filter( {$0.isSelected == true} )

        s += "Data reports:"
        if selectedFromData.count > 0 {
            selectedFromData.forEach { ob in
                let obID = ob.id ?? -1
                s += " \(obID)"
            }
        } else {
            s += " Nothing selected"
        }
        s += "\n"
        s += "Table reports: "
        if let selectedFromTable = tableView.indexPathsForSelectedRows {
            selectedFromTable.forEach { idx in
                s += " \(idx.row)"
            }
        } else {
            s += " No rows selected"
        }
        infoLabel.text = s
    }
}

extension SelMethodTableViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return pincodes.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "toggleCell", for: indexPath) as! MyToggleCell
        c.label.text = pincodes[indexPath.row].name
        c.selectionStyle = .none
        return c
    }
    func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        // is a row already selected?
        if let idx = tableView.indexPathForSelectedRow {
            if idx == indexPath {
                // tapped row is already selected, so
                //  deselect it
                tableView.deselectRow(at: indexPath, animated: false)
                //  update our data
                pincodes[indexPath.row].isSelected = false
                //  tell table view NOT to select the row
                return nil
            } else {
                // some other row is selected, so
                //  update our data
                //  table view will automatically deselect that row
                pincodes[idx.row].isSelected = false
            }
        }
        // tapped row should now be selected, so
        //  update our data
        pincodes[indexPath.row].isSelected = true
        //  tell table view TO select the row
        return indexPath
    }
}

class MyToggleCell: UITableViewCell {
    let imgView: UIImageView = {
        let v = UIImageView()
        return v
    }()
    let label: UILabel = {
        let v = UILabel()
        return v
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        [imgView, label].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(v)
        }
        let g = contentView.layoutMarginsGuide
        
        // give bottom anchor less-than-required
        //  to avoid auto-layout complaints
        let b = imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0)
        b.priority = .required - 1
        
        NSLayoutConstraint.activate([
            imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            imgView.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
            imgView.widthAnchor.constraint(equalToConstant: 32.0),
            imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
            b,
            
            label.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
            label.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 16.0),
            label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
        ])
        if let img1 = UIImage(systemName: "square"),
           let img2 = UIImage(systemName: "checkmark.square") {
            imgView.image = img1
            imgView.highlightedImage = img2
        }
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        imgView.isHighlighted = selected ? true : false
    }
}

它看起来像这样:

在此处输入图像描述

运行时:

  • 点击一行将选择该行
  • 点击不同的行将选择新行并取消选择当前选择的行
  • 点击已经选择的行将取消选择它
  • 点击灰色的“信息视图”将报告来自数据和表格视图的选择状态

在此处输入图像描述

在此处输入图像描述

请注意,如果选定的行滚动到视图之外,它将保持选中状态(并且在滚动回视图时将显示为选中状态)并且数据和表视图选择状态将继续正确。


编辑

如果我们想使用didSelectRowAt(也许用于其他用途),我们可以像这样“切换”选定的行:

func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
    
    // if the tapped row is already selected
    if let indexPathForSelectedRow = tableView.indexPathForSelectedRow,
       indexPathForSelectedRow == indexPath {
        tableView.deselectRow(at: indexPath, animated: false)
        // calling .deselectRow(at: ...) does NOT trigger a call to didDeselectRowAt
        // so update our data here
        pincodes[indexPath.row].isSelected = false
        return nil
    }
    return indexPath
    
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("did select", indexPath)
    pincodes[indexPath.row].isSelected = true
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    print("did deselect", indexPath)
    pincodes[indexPath.row].isSelected = false
}
于 2021-12-10T22:00:49.333 回答
1

对于问题 1 - 通过使用这行代码:

var pincodePreviousIndex: Int = 0

在单击另一行之前,您无法单击第一行,因为

pincodes[pincodePreviousIndex].isSelected = false

由于您在开始时默认为 0,因此与第一行相关。

对于问题 2 - 如果您选择第 2 行(已选择),然后再次选择它以取消选择它: pincodePreviousIndex 将保留该行的值,然后再次取消选择它

pincodes[pincodePreviousIndex].isSelected = false

因此,即使您选择它,它也会取消选择它。

我会在顶部这样做: var pincodePreviousIndex: Int = -1

在底部:

if pincodePreviousIndex > 0 && pincodePreviousIndex != indexPath.row {
    pincodes[pincodePreviousIndex].isSelected = false
}
于 2021-12-10T19:08:29.817 回答