Проблема с выбором и отменой выбора ячейки tableview в swift

#ios #swift #uitableview #select #deselect

#iOS #быстрый #uitableview #выбирать #отменить выбор

Вопрос:

Я показываю пинкоды в tableview, и когда я выбираю ячейку, она должна выбрать, и если я снова нажму на ту же ячейку, то она должна отменить выбор(при нажатии на ячейку должно работать как переключатель).

введите описание изображения здесь

но с нижеприведенным кодом

проблема 1: сначала я не могу выбрать 1-ю строку, но после выбора любой другой строки, а затем могу выбрать 1-ю строку.. почему? где я ошибаюсь?

проблема 2: только один раз я могу выбрать отменить выбор одной и той же строки двумя нажатиями, если я нажимаю 3 раза подряд, а затем не могу выбрать одну и ту же строку, почему?.. пожалуйста, направьте

 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) -gt; 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 gt; 0 amp;amp; pincodePreviousIndex != indexPath.row { pincodes[pincodePreviousIndex].isSelected = false } pincodePreviousIndex = indexPath.row  }  

this is working as i want when i select from index = 1, but if i select first row(index = 0) then the right mark remains if i select another row, why?

o/p с отредактированным кодом:

введите описание изображения здесь

Комментарии:

1. Вы разрешаете несколько вариантов выбора? Или только один выбор, и вы хотите переключить выбор при нажатии на одну и ту же строку… одновременно сняв флажок «другая выбранная строка» (если она есть)?

Ответ №1:

Для проблемы 1 — С помощью этой строки кода:

var pincodePreviousIndex: Int = 0

Вы не можете щелкнуть первую строку, пока не нажмете другую, так как

pincodes[pincodePreviousIndex].isSelected = false

Поскольку вы по умолчанию устанавливаете значение 0 в начале, это соответствует первой строке.

Для проблемы 2 — если вы выберете строку 2 (выбрана), а затем снова выберите ее, чтобы отменить ее выбор: pincodePreviousIndex сохранит значение этой строки, а затем снова отменит ее выбор с помощью

pincodes[pincodePreviousIndex].isSelected = false

Поэтому, даже если вы его выберете, он отменит его выбор.

Я бы сделал это наверху: var pincodePreviousIndex: Int = -1

и в самом низу:

 if pincodePreviousIndex gt; 0 amp;amp; pincodePreviousIndex != indexPath.row {  pincodes[pincodePreviousIndex].isSelected = false }  

Комментарии:

1. Хорошее объяснение проблемы, но это быстро. Нам не нужно использовать значения sentinel. У нас есть варианты. Объявите var pincodePreviousInstance: Int? , а затем используйте условное разворачивание.

Ответ №2:

Есть пара подходов, которые вы можете предпринять, чтобы избавить себя от некоторых проблем.

Сначала установите .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) -gt; 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..lt;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) -gt; Void {  var s: String = ""    let selectedFromData = pincodes.filter( {$0.isSelected == true} )   s  = "Data reports:"  if selectedFromData.count gt; 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) -gt; Int {  return pincodes.count  }  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -gt; 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) -gt; 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() -gt; 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  } }  

Это будет выглядеть так:

введите описание изображения здесь

При запуске:

  • Нажатие на строку выделит эту строку
  • Нажатие на другую строку выделит новую строку и отменит выбор текущей выбранной строки
  • Нажатие на уже выбранную строку отменит ее выбор
  • Нажатие на серое «представление информации» приведет к сообщению о состояниях выбора как из данных, так и из представления таблицы

введите описание изображения здесь

введите описание изображения здесь

Note that if a selected row is scrolled out-of-view, it will remain selected (and will show selected when scrolled back into view) and the data and table view selection states will continue to be correct.


Edit

If we want to use didSelectRowAt (perhaps for other uses), we can «toggle» the selected row like this:

 func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -gt; 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 }