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


#1

Добрый день, добрый вечер!
Не могу до переть, как мне правильно обновить ячейку, суть загвоздки такая:

  1. При первой создании задачи, задача сохраняться в базу данных Realm, и обновления таблицы происходить мгновенно экран “Main”, при переходе на экран “Satistic” все очень классно, обновления произошло.

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

Вопрос: Подскажите, как можно реализовать перезагрузку таблицы, при добавлении новой задачи?

Код прилогается:

Первый экран:

private let localRealm = try! Realm()
private var workoutArray: Results<WorkoutModel>!
private var userArray: Results<UserModel>!
private let one = StatisticViewController()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    getWorkouts(date: Date())
    
    tableView.reloadData()
    setupUserParameters()
}

override func viewDidLoad() {
    super.viewDidLoad()
    userArray = localRealm.objects(UserModel.self)
    setupView(calendarView, addWorkoutButton,workoutTodayLabel, weatherView,tableView, noWorkoutImageView)
    setupUserParameters()
    setConstrains()
    setDelegate()
}

private func setupUserParameters() {
    if userArray.count != 0 {
        calendarView.userNameLabel.text = userArray[0].userFirstName + userArray[0].userSecondName
    
        guard let data = userArray[0].userImage else { return }
        guard let image = UIImage(data: data) else { return }
        calendarView.imagePerson.image = image
    }
}

private func getWorkouts(date: Date) {
    let dateTimeZone = date
    let weekday = dateTimeZone.getWeekdayNumber()
    let dateStart = dateTimeZone.startEndDate().0
    let dateEnd = dateTimeZone.startEndDate().1
    
    let predicateRepeat = NSPredicate(format: "workoutNumberOfDay = \(weekday) AND workoutRepeat = true")
    let predicateUnrepeat = NSPredicate(format: "workoutRepeat = false AND workoutDate BETWEEN %@", [dateStart, dateEnd])
    let compound = NSCompoundPredicate(type: .or, subpredicates: [predicateRepeat, predicateUnrepeat])
    
    workoutArray = localRealm.objects(WorkoutModel.self).filter(compound).sorted(byKeyPath: "workoutName")
    
    checkWorkoutsToday()
    tableView.reloadData()
}

private func checkWorkoutsToday() {
    if workoutArray.count == 0 {
        tableView.isHidden = true
        noWorkoutImageView.isHidden = false
    } else {
        tableView.isHidden = false
        noWorkoutImageView.isHidden = true
        tableView.reloadData()
    }
}


@objc private func addWorkoutButtonTapped() {
    let newWorkoutViewController = NewWorkoutViewController()
    newWorkoutViewController.modalPresentationStyle = .fullScreen
    present(newWorkoutViewController, animated: true)
}

}

Второй экран:

private let localRealm = try! Realm()
private var workoutArray: Results<WorkoutModel>!

private var differenceArray = [DifferenceWorkout]()
private var filtredArray = [DifferenceWorkout]()

private let dateToday = Date().localDate()
private var isFiltred = false

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    tableView.reloadData()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    keyboardManagerVisible(false)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    keyboardManagerVisible(true)
}

override func viewDidLoad() {
    super.viewDidLoad()
    setupView(statisticLabel, segmentedControl, nameTextField, exercisesLabel, tableView)
    setConstraints()
    setDelegate()
    setStartScreen()
}

private func addTaps() {
    let tapScreen = UITapGestureRecognizer(target: self, action: #selector(hideKeyboeard))
    tapScreen.cancelsTouchesInView = false
    view.addGestureRecognizer(tapScreen)
}

private func setDelegate() {
    nameTextField.delegate = self
    
    tableView.delegate = self
    tableView.dataSource = self
    tableView.register(StatisticTableViewCell.self, forCellReuseIdentifier: identifierCell)
}


private func getWorkoutsName() -> [String] {

    var nameArray = [String]()
    workoutArray = localRealm.objects(WorkoutModel.self)

    for workoutModel in workoutArray {
        if !nameArray.contains(workoutModel.workoutName) {
            nameArray.append(workoutModel.workoutName)
        }
    }
    return nameArray
}

private func getDifferenceModel(dateStart: Date) {
    
    let dateEnd = Date().localDate()
    let nameArray = getWorkoutsName()
    
    for name in nameArray {
        
        let predicateDifference = NSPredicate(format: "workoutName = '\(name)' AND workoutDate BETWEEN %@", [dateStart, dateEnd])
        workoutArray = localRealm.objects(WorkoutModel.self).filter(predicateDifference).sorted(byKeyPath: "workoutDate")
     
        guard let last = workoutArray.last?.workoutReps,
              let first = workoutArray.first?.workoutReps else {
                  return
              }
        let differenceWorkout = DifferenceWorkout(name: name, lastReps: last, firstReps: first)
        differenceArray.append(differenceWorkout)
    }
}

private func setStartScreen() {
    getDifferenceModel(dateStart: dateToday.offsetDays(days: 6))
}


private func filtringWorkouts(text: String) {
    
    for workout in differenceArray {
        if workout.name.lowercased().contains(text.lowercased()) {
            filtredArray.append(workout)
        }
    }
}

@objc private func segmentChanged() {
    if segmentedControl.selectedSegmentIndex == 0 {
        differenceArray = [DifferenceWorkout]()
        let dateStart = dateToday.offsetDays(days: 6)
        getDifferenceModel(dateStart: dateStart)
        tableView.reloadData()
    } else {
        differenceArray = [DifferenceWorkout]()
        let dateStart = dateToday.offsetMonth(month: 1)
        getDifferenceModel(dateStart: dateStart)
        tableView.reloadData()
    }
   
}

@objc private func hideKeyboeard() {
    view.endEditing(true)
}

}


#2

Делайте подписку на изменение коллекции в базе.
https://www.mongodb.com/docs/realm/sdk/swift/react-to-changes/


#3

Спасибо за документацию! Таблица с новыми данными обновляется.
Но появилась еще одна проблема, при удалении одно записи, почему то приложения падает.

Вылетает вот такая ошибка: - "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). "

Так же при добавлении новой записи, через несколько минут приложения падает.
Выдает ошибку: “attempt to insert row 3 into section 0, but there are only 3 rows in section 0 after the update”

Подскажите, где я ошибся?

Скопированный код из документации:
private var notificationToken: NotificationToken? = nil

private func reloadTableView() {
    let result = localRealm.objects(WorkoutModel.self)
    notificationToken = result.observe { [weak self] (changes: RealmCollectionChange) in
        guard let tableView = self?.tableView else { return }
        switch changes {
        case .initial(_):
            tableView.reloadData()
            break
        case .update(_, deletions: let deletions, insertions: let insertions, modifications: let modifications):
            tableView.performBatchUpdates {
                
                tableView.deleteRows(at: deletions.map({IndexPath(row: $0, section: 0)}), with: .automatic)
                tableView.insertRows(at: insertions.map({IndexPath(row: $0, section: 0)}), with: .automatic)
                tableView.reloadRows(at: modifications.map({IndexPath(row: $0, section: 0)}), with: .automatic)
            }

        case .error(_):
            fatalError("Nooo")
        }
        
    }
}

deinit {
    notificationToken?.invalidate()
}

#4

У вас этот метод вызывается один раз при инициализации или в разных местах по условиям?

Еще у вас тут локальная переменная
let result = localRealm.objects(WorkoutModel.self)

Что тогда вы используете в самой таблице?


#5

Ход логики такая: Я беру данные с Realm с первого экрана и отравляю их в простую структуру модели на втором экране. Для заполнения таблица.

Вот мой код второго класса без вьюшек:

 struct DifferenceWorkout {
     let name: String
     let lastReps: Int
     let firstReps: Int
 }



private let localRealm = try! Realm()
private var workoutArray: Results<WorkoutModel>!
private var notificationToken: NotificationToken? = nil

private var differenceArray = [DifferenceWorkout]()
private var filtredArray = [DifferenceWorkout]()

private let dateToday = Date().localDate()
private var isFiltred = false

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    tableView.reloadData()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    keyboardManagerVisible(false)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    keyboardManagerVisible(true)
}

override func viewDidLoad() {
    super.viewDidLoad()
    setupView(statisticLabel, segmentedControl, nameTextField, exercisesLabel, tableView)
    setConstraints()
    setDelegate()
    setStartScreen()
}

private func reloadTableView() {
    notificationToken = workoutArray.observe { [weak self] (changes: RealmCollectionChange) in
        guard let tableView = self?.tableView else { return }
        switch changes {
        case .initial(_):
            tableView.reloadData()
            
        case .update(_, deletions: let deletions, insertions: let insertions, modifications: let modifications):
            tableView.performBatchUpdates {
                
                tableView.deleteRows(at: deletions.map({IndexPath(row: $0, section: 0)}), with: .automatic)
                tableView.insertRows(at: insertions.map({IndexPath(row: $0, section: 0)}), with: .automatic)
                tableView.reloadRows(at: modifications.map({IndexPath(row: $0, section: 0)}), with: .automatic)
            }

        case .error(_):
            fatalError("Nooo")
        }
    }
}

deinit {
    notificationToken?.invalidate()
}

private func getWorkoutsName() -> [String] {

    var nameArray = [String]()
    workoutArray = localRealm.objects(WorkoutModel.self)

    for workoutModel in workoutArray {
        if !nameArray.contains(workoutModel.workoutName) {
            nameArray.append(workoutModel.workoutName)
        }
    }
    return nameArray
}

private func getDifferenceModel(dateStart: Date) {
    
    let dateEnd = Date().localDate()
    let nameArray = getWorkoutsName()
    
    for name in nameArray {
        
        let predicateDifference = NSPredicate(format: "workoutName = '\(name)' AND workoutDate BETWEEN %@", [dateStart, dateEnd])
        workoutArray = localRealm.objects(WorkoutModel.self).filter(predicateDifference).sorted(byKeyPath: "workoutDate")
     
        guard let last = workoutArray.last?.workoutReps, let first = workoutArray.first?.workoutReps else { return }
        
        let differenceWorkout = DifferenceWorkout(name: name, lastReps: last, firstReps: first)
        differenceArray.append(differenceWorkout)
    }
}

private func setStartScreen() {
    getDifferenceModel(dateStart: dateToday.offsetDays(days: 6))
    reloadTableView()
    tableView.reloadData()
}


private func filtringWorkouts(text: String) {
    
    for workout in differenceArray {
        if workout.name.lowercased().contains(text.lowercased()) {
            filtredArray.append(workout)
        }
    }
}

@objc private func segmentChanged() {
    if segmentedControl.selectedSegmentIndex == 0 {
        differenceArray = [DifferenceWorkout]()
        let dateStart = dateToday.offsetDays(days: 6)
        getDifferenceModel(dateStart: dateStart)
      
    } else {
        differenceArray = [DifferenceWorkout]()
        let dateStart = dateToday.offsetMonth(month: 1)
        getDifferenceModel(dateStart: dateStart)
       
    }
   
}

@objc private func hideKeyboeard() {
    view.endEditing(true)
}

}

extension StatisticViewController: UITextFieldDelegate {

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if let text = textField.text, let textRange = Range(range, in: text) {
        let updatedText = text.replacingCharacters(in: textRange, with: string)
        filtredArray = [DifferenceWorkout]()
        isFiltred = (updatedText.count > 0 ? true : false)
        filtringWorkouts(text: updatedText)
        tableView.reloadData()
    }
    return true
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    nameTextField.resignFirstResponder()
}

func textFieldShouldClear(_ textField: UITextField) -> Bool {
    isFiltred = false
    differenceArray = [DifferenceWorkout]()
    getDifferenceModel(dateStart: dateToday.offsetDays(days: 7))
    tableView.reloadData()
    return true
}

}

extension StatisticViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
55
}
}

extension StatisticViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
isFiltred ? filtredArray.count : differenceArray.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: identifierCell, for: indexPath) as! StatisticTableViewCell
    let differenceModel = (isFiltred ? filtredArray[indexPath.row] : differenceArray[indexPath.row])
    
    cell.cellConfigure(differenceWorkout: differenceModel)
    
    return cell
}

}


#6

У вас все не правильно. Ваш workoutArray не используется как нужно в таблице, но вы пытаетесь обновить таблицу основываясь на индексах из workoutArray, хотя в таблице используются совсем друггие массивы, из-за этого у вас и ошибки, т.к. данные в таблице не совпадают с данными в изначальном массиве.


#7

А ну да, логично!) Пойду переделывать все)