Вопрос или проблема
У меня есть замыкание в провайдере секции в инициализаторе дифференцируемого источника данных, где я устанавливаю переменную checkCellView
изнутри замыкания. Когда я вызываю это замыкание из switchCheckSelectionFunc()
в CheckCellView
, выбирается неверная ячейка. Как я могу это исправить?
class QuestionnaireViewController: UIViewController {
var checkCellView: CheckCellView? {
didSet {
if checkCellView != nil {
print("тип замыкания,", type(of: checkCellView!.switchCheckSelection))
} else {
}
}
}
func configureDataSource() {
dataSource = CustomTableViewDiffableDataSource(tableView: tableView, cellProvider: { [self] tableView, indexPath, formItem in
// print("раздел,", indexPath.section)
self.checkCellView?.switchCheckSelection = { checkIsSelected in
if checkIsSelected {
checkCellView?.checkImageView.image = UIImage(named: "check-box-selected")!
} else {
checkCellView?.checkImageView.image = UIImage(named: "check-box-deselected")!
}
}
switch formItem {
case .name(let name):
let cell = tableView.dequeueReusableCell(withIdentifier: PatientInfoCell.reuseID, for: indexPath) as! PatientInfoCell
cell.configure(title: "Имя")
return cell
case .lastName(let lastName):
let cell = tableView.dequeueReusableCell(withIdentifier: PatientInfoCell.reuseID) as! PatientInfoCell
cell.configure(title: "Фамилия")
return cell
case .age(let age):
let cell = tableView.dequeueReusableCell(withIdentifier: PatientInfoCell.reuseID) as! PatientInfoCell
cell.configure(title: "Возраст")
return cell
case .gender(let gender):
let cell = tableView.dequeueReusableCell(withIdentifier: GenderCell.reuseID) as! GenderCell
cell.titleView.text = "Пол"
cell.femaleIcon.image = UIImage(named: "female-icon-deselected")!
cell.switchFemaleIconTapped = { isSelected in
if isSelected {
cell.femaleIcon.image = UIImage(named: "female-icon-selected")!
} else {
cell.femaleIcon.image = UIImage(named: "female-icon-deselected")!
}
}
cell.maleIcon.image = UIImage(named: "male-icon-deselected")!
cell.switchMaleIconTapped = {
if $0 {
cell.maleIcon.image = UIImage(named: "male-icon-selected")!
} else {
cell.maleIcon.image = UIImage(named: "male-icon-deselected")!
}
}
return cell
case .indwellingCardiacDeviceOrProsthesis(let bool):
checkCellView = tableView.dequeueReusableCell(withIdentifier: CheckCellView.reuseID, for: indexPath) as? CheckCellView
checkCellView?.checkImageView.image = UIImage(named: "check-box-deselected")!
checkCellView?.switchCheckSelection = { checkIsSelected in
if checkIsSelected {
checkCellView?.checkImageView.image = UIImage(named: "check-box-selected")!
} else {
checkCellView?.checkImageView.image = UIImage(named: "check-box-deselected")!
}
}
return checkCellView
case .predisposingHeartCondition(let bool):
checkCellView = tableView.dequeueReusableCell(withIdentifier: CheckCellView.reuseID, for: indexPath) as? CheckCellView
checkCellView?.checkImageView.image = UIImage(named: "check-box-deselected")!
return checkCellView
case .intravenousDrugUse(let bool):
checkCellView = tableView.dequeueReusableCell(withIdentifier: CheckCellView.reuseID, for: indexPath) as? CheckCellView
checkCellView?.checkImageView.image = UIImage(named: "check-box-deselected")!
return checkCellView
case .cardiacStructuralDisorder(let bool):
checkCellView = tableView.dequeueReusableCell(withIdentifier: CheckCellView.reuseID, for: indexPath) as? CheckCellView
checkCellView?.checkImageView.image = UIImage(named: "check-box-deselected")!
return checkCellView
case .diabetic(let bool):
checkCellView = tableView.dequeueReusableCell(withIdentifier: CheckCellView.reuseID, for: indexPath) as? CheckCellView
checkCellView?.checkImageView.image = UIImage(named: "check-box-deselected")!
return checkCellView
}
})
}
}
class CheckCellView: UITableViewCell {
var titleView: UILabel = UILabel()
var checkImageView: UIImageView = UIImageView()
static var reuseID = "check-cell-reuseID"
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUp()
}
var switchCheckSelection: ((_ isSelected: Bool) -> Void)?
var checkIsSelected: Bool = false
func setUp() {
checkImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(switchCheckSelectionFunc)))
checkImageView.isUserInteractionEnabled = true
let stackView = UIStackView(arrangedSubviews: [
titleView,
checkImageView
])
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor)
])
}
@objc func switchCheckSelectionFunc() {
checkIsSelected.toggle()
switchCheckSelection?(checkIsSelected)
}
func applySnapshot() {
guard case let .questionnaireData(formData) = data,
let formData = formData else {
return
}
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections(Section.allCases)
snapshot.appendItems(formData.patientInfoData, toSection: .patientInformation)
snapshot.appendItems(formData.checkAllThatApplyData, toSection: .checkAllThatApply)
dataSource.apply(snapshot, animatingDifferences: true)
}
}
Ответ или решение
Как вызвать замыкание для правильного подвиджета в UITableView
Ваша проблема связана с тем, что переменная checkCellView
используется в замыкании, задаваемом в dataSource
, что приводит к непредсказуемым результатам при вызове замыкания в методе switchCheckSelectionFunc()
класса CheckCellView
. Чтобы правильно связать замыкание с конкретным экземпляром CheckCellView
, необходимо модифицировать вашу реализацию так, чтобы замыкание было привязано к конкретному ячейке на основе indexPath
, а не к общей переменной checkCellView
.
Устранение проблемы
Вот несколько шагов, которые помогут решить вашу проблему:
-
Избегайте глобальной ссылки на
checkCellView
: Вместо использования единственного свойстваcheckCellView
, передавайте ссылку на текущий объект ячейки, возвращаемый изcellProvider
. -
Сохраняйте состояние выбора: При использовании
UITableView
важно сохранять состояние каждого элемента (например, выбран ли чекбокс), чтобы правильно обновить интерфейс при повторной загрузке данных. Используйте массив или словарь для хранения состояния для каждого элемента.
Вот пример, как можно переписать ваш код для достижения этой цели:
Пример кода
class QuestionnaireViewController: UIViewController {
var selectionStates: [IndexPath: Bool] = [:] // Хранит состояния выбора для каждой ячейки
func configureDataSource() {
dataSource = CustomTableViewDiffableDataSource(tableView: tableView, cellProvider: { [unowned self] tableView, indexPath, formItem in
// Дефолтное состояние для чекбокса
let isSelected = selectionStates[indexPath] ?? false
switch formItem {
// обработка других ячеек
case .indwellingCardiacDeviceOrProsthesis:
let cell = tableView.dequeueReusableCell(withIdentifier: CheckCellView.reuseID, for: indexPath) as! CheckCellView
cell.checkImageView.image = isSelected ? UIImage(named: "check-box-selected") : UIImage(named: "check-box-deselected")
// Сохраняем состояние выбора в замыкании
cell.switchCheckSelection = { [weak self] checkIsSelected in
self?.selectionStates[indexPath] = checkIsSelected // Сохраняем состояние
cell.checkImageView.image = checkIsSelected ? UIImage(named: "check-box-selected") : UIImage(named: "check-box-deselected")
}
return cell
// и другие случаи
}
})
}
}
Изменения в коде
- Используйте массив или словарь: Мы добавили словарь
selectionStates
, который хранит состояния выбора для каждой ячейки по еёIndexPath
. - Передача переменной в замыкание: Вместо использования общего свойства
checkCellView
, мы передаем текущее состояние выбранной ячейки в замыкание вызова. Это гарантирует, что состояние будет относиться только к конкретной ячейке, которую мы на данный момент обрабатываем.
Заключение
Такое изменение улучшает управление состоянием ячеек и позволяет избежать непредвиденных проблем, связанных с неверными ссылками на замыкания. Важно запомнить, что в программировании особенно важно контролировать память и ссылки, чтобы избежать "призраков" и неправильного поведения интерфейса. Подход с использованием IndexPath
обеспечивает индивидуальность каждого элемента и более высокую стабильность поведения вашего UITableView.
Применяя вышеизложенные практики, вы сможете корректно обрабатывать события выбора внутри ваших ячеек, и ваше приложение станет более удобным и надежным для пользователя.