#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 }