Как я могу вызвать замыкание для правильной подвидж?

Вопрос или проблема

У меня есть замыкание в провайдере секции в инициализаторе дифференцируемого источника данных, где я устанавливаю переменную 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.

Устранение проблемы

Вот несколько шагов, которые помогут решить вашу проблему:

  1. Избегайте глобальной ссылки на checkCellView: Вместо использования единственного свойства checkCellView, передавайте ссылку на текущий объект ячейки, возвращаемый из cellProvider.

  2. Сохраняйте состояние выбора: При использовании 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.

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

Оцените материал
Добавить комментарий

Капча загружается...