Как определить индекс ячейки, где нажали кнопку?

swift
tableview

#1

В каждой ячейке таблицы у меня есть кнопка, при нажатии на которую на следующий viewcontroller передаётся уникальное число из массива:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "goToRatingModal" {
        guard let dvc = segue.destination as? ModalRatingVC else { return }
        dvc.doctorID = appointments[].doctor.id
    }
}

Пробовал tableView.indexForSelectedRow, но приходит nil, так как по факту нажатия на ячейку-то не происходит.

Как можно “определить” индекс ячейки, в области которой произошло нажатие на кнопку?


#2

Нужно определить тег для вашей кнопки в функции

func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.yourButton.addTarget( self , action: #selector (setNumberFromArray), for: .touchUpInside)
cell.yourButton.tag = indexPath.row
        return cell
    }

где yourButton - ваша UIButton

Затем

@objc func setNumberFromArray( _ button: UIButton) {
let buttonTag = button.tag
let vc = ModalRatingVC()
vc.doctorID = yourArray[buttonTag]
self.parentController!.navigationController?.pushViewController(vc, animated: false)
}

где yourArray - ваш массив

и добавить расширение

    // MARK: Родительский ViewController для UIView
    extension UIView {
        var parentController: UIViewController? {
            var parentResponder: UIResponder? = self
            
            while parentResponder != nil {
                parentResponder = parentResponder!.next
                
                if let viewController = parentResponder as? UIViewController {
                    return viewController
                }
            }
            return nil
        }
    }

#3

Спасибо. А для чего нужен extension? Не совсем понимаю…


#4

Он нужен, чтобы определить родительский ViewController для tableView, использование происходит вот в этой строке:

self.parentController!.navigationController?.pushViewController(vc, animated: false)
}

Если у вас класс с tableView вынесен в отдельный от ViewController’а.
Если вы работаете в одном классе, тогда extension не нужен, и строку

self.parentController!.navigationController?.pushViewController(vc, animated: false)

можно заменить на self.navigationController?.pushViewController(vc, animated: false)


#5

Сделал всё как у вас, принтится пустое значение - https://i.imgur.com/VvZa77L.png Вот такой принт - https://i.imgur.com/aKh6AZq.png

nil - это print на значение, которое мне нужно передать.

Таблица у меня встроена во ViewController. Соответственно, экстеншен, как я понимаю, мне не нужен.

Что я делаю не так?


#6

А такой принт что выводит print(appointments[buttonTag].doctor.id) ?


#7

Всё равно: без изменений.
-1 я поставил, так как нумерация вроде с 1 начинается. А в массиве нумерация с 0. Изначально я пробовал решить задачу таким способом:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let cell = CustomCellForAppointmentsVC()
    if segue.identifier == "goToRatingModal" {
        guard let dvc = segue.destination as? ModalRatingVC else { return }
        dvc.doctorID = appointments[cell.leaveRatingButton.tag].doctor.id
    }
}

Но приходит nil.


#8
  1. () вывелось, потому что у вас в принте была указана инициализация, а не значение параметра

  2. -1 не нужен, тк нумерация indexPath.raw тоже будет с 0
    (в частности в функции tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath), из которой получаем значение тега cell.yourButton.tag = indexPath.row)

  3. как вариант можно еще попробовать

     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
             if segue.identifier == "goToRatingModall" {
                 guard let indexPath = tableView.indexPathForSelectedRow else { return }
                 guard let dvc = segue.destination as? ModalRatingVC else { return }
                 dvc.doctorID = appointments[indexPath.raw].doctor.id
             }
         }

#9

Третий вариант срабатывает только если сначала нажать на ячейку, а потом нажать на кнопку.


#10

У меня и первый вариант работает с обычным массивом…
Если в функции setNumberFromArray вывести на экран print(button.tag ) - что-нибудь выводит?


#11

Да, выводит тег. Проблема в том, что на второй контроллер инфа не передаётся у меня(


#12

Почитайте про делегаты и работать станет проще с такими задачами.
На крайний случай, можно сделать замыкание для кнопки в методе cellForRowAt.


#13

почему сразу крайний случай то :roll_eyes:


#14

По идее метод setNumberFromArray( _ button: UIButton) (вместо prepare(for segue: UIStoryboardSegue, sender: Any?)) должен корректно передать значение:

@objc func setNumberFromArray( _ button: UIButton) {
let buttonTag = button.tag
let vc = ModalRatingVC()
vc.doctorID = appointments[buttonTag].doctor.id
self.navigationController?.pushViewController(vc, animated: false)
}

Попробуйте в классе ModalRatingVC в методе viewDidLoad вывести значение print(doctorID)
А также в setNumberFromArray можно еще вывести print(appointments[buttonTag].doctor.id) и
print(vc.doctorID) после строки vc.doctorID = appointments[buttonTag].doctor.id


#15

Покажите ваш VC целиком.


#16

Вот отсюда:

class AppointmentsVC: UIViewController {

var appointments: [Appointment] = []

@IBOutlet var tableView: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()
    
    fetchDataForTableView()
}

// MARK: - Fetch Data

private func fetchDataForTableView() {
    NetworkManager.fetchAllAppointments { (appointmetns) in
        self.appointments = appointmetns
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
}

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let cell = CustomCellForAppointmentsVC()
    if segue.identifier == "goToRatingModal" {
        // на всякий случай попробую еще так
        guard let indexPath = tableView.indexPathForSelectedRow else { return }
        guard let dvc = segue.destination as? ModalRatingVC else { return }
        dvc.doctorID = appointments[indexPath.row].doctor.id
    }
}

}

extension AppointmentsVC: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return appointments.count + 1
    }

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.row == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: "makeAppointment", for: indexPath) as? CustomCellForAppointmentsVC
        return cell ?? UITableViewCell()
    } else {
        let cell = tableView.dequeueReusableCell(withIdentifier: "appointmentCell", for: indexPath) as! CustomCellForAppointmentsVC
        let appointment = appointments[indexPath.row - 1]
        
        cell.date.text = "\(appointment.date) \(appointment.time_start)"
        cell.nameOfClinic.text = appointment.filial.name
        cell.placeOfClinic.text = appointment.filial.address
        cell.doctorName.text = appointment.doctor.name
        cell.doctorSpecialize.text = appointment.doctor.specialty
        cell.leaveRatingButton.addTarget(self, action: #selector(setNumberFromArray), for: .touchUpInside)
        cell.leaveRatingButton.tag = indexPath.row
        
        
        cell.secondCellView.setupViewForBonusScreen()
        
        NetworkManager.fetchImage(path: appointment.filial.image ?? "1599117490.jpeg") { (imageData) in
            cell.imageOfClinic.image = UIImage(data: imageData)
        }
        
        NetworkManager.fetchImage(path: appointment.doctor.photo ?? "1599117490.jpeg") { (imageData) in
            cell.imageOfDoctor.image = UIImage(data: imageData)
        }
        
        return cell
    }
}

@objc private func setNumberFromArray(_ button: UIButton) {
    let buttonTag = button.tag
    let vc = ModalRatingVC()
    vc.doctorID = buttonTag
    self.navigationController?.pushViewController(vc, animated: false)
}

}

…нужно передать doctorID сюда:

class ModalRatingVC: UIViewController {

var doctorID: Int?

@IBOutlet var ratingStackView: RatingControl!
@IBOutlet var commentTF: UITextField!
@IBOutlet var publishButton: UIButton!
@IBOutlet var ratingView: UIView!

override func viewDidLoad() {
    super.viewDidLoad()

    setupViews()
    print(doctorID)
}

private func setupViews() {
    ratingView.setupViewForBonusScreen()
    publishButton.setupPinkButton()
    commentTF.setupCustomTF()
}

@IBAction func closeModal(_ sender: UIButton) {
    dismiss(animated: true, completion: nil)
}

@IBAction func publishRating(_ sender: UIButton) {
    guard let doctorID = doctorID, let comment = commentTF.text else { return }
    let body: [String: Any] = ["rate": ratingStackView.rating,
                               "text": comment,
                               "user_id": 123,
                               "doctor_id": doctorID]
    
    NetworkManager.publishRating(body: body) {
         // close modal
    }
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    view.endEditing(true)
}

}


#17

Пробовал. На первом VC значение есть. На ratingVC - nil. Вот - https://i.imgur.com/UdOzJ7h.png


#18

А пример можно с замыканием?)


#19

При формировании ячейки

cell.buttonTapHandler = {
    // делаем все что нужно в рамках данного VC
    // получаем данные для конкретной ячейки
    let data = self.someArray[indexPath.row]
}

В самой ячейке

var buttonTapHandler: (() -> ())?

@IBAction func buttonTap() {
    buttonTapHandler()?
}

#20

Дабрый день. Спасибо! Данные теперь передаются, только почему-то с задержкой - https://i.imgur.com/sQ77S6m.png

То есть чтобы в переменной были актуальные данные нужно нажать на кнопку, перейти на VC, закрыть VC и нажать на кнопку ещё раз.