Ok, the setup for this is pretty complex. I'm doing maintenance on an app I did not write. The view structure is as follows:
At the base you have a UICollectionViewController. Each UICollectionViewCell has an assigned UIViewController within it. Each of these controllers is attached to the view hierarchy as a child view controller. The main view of one of these child controllers is a UITableView/UIViewController pair.
Everything seems to be hooked up properly and working well except for one case. Assuming the table-view has more than two rows, If I swipe-left to delete the second row (IndexPath 0,1), the content or row one (IndexPath 0,0) goes blank. I can see the disclosure accessory view but the two UILabels up and disappear. If I tap on the blank row the tap behavior is still intact. If I refresh the table-view, the missing content appears as it should.
To make things a little more interesting, this table-view is implement using RxSwift. Here is the code:
Question: Why is the first row content being wiped out? Assuming no one can know the answer to this I have a follow-up question. How can I get the contents of the entire tableview to redraw themselves after a successful delete? I would have assumed the binding to the table-view on line 27 would handle this automatically when the view-model updates.
At the base you have a UICollectionViewController. Each UICollectionViewCell has an assigned UIViewController within it. Each of these controllers is attached to the view hierarchy as a child view controller. The main view of one of these child controllers is a UITableView/UIViewController pair.
Everything seems to be hooked up properly and working well except for one case. Assuming the table-view has more than two rows, If I swipe-left to delete the second row (IndexPath 0,1), the content or row one (IndexPath 0,0) goes blank. I can see the disclosure accessory view but the two UILabels up and disappear. If I tap on the blank row the tap behavior is still intact. If I refresh the table-view, the missing content appears as it should.
To make things a little more interesting, this table-view is implement using RxSwift. Here is the code:
Code Block private func bind() { // Cell for row at index path. let curriedArgument: (Int, Assignment, AssessmentTableViewCell) -> Void = { /* rowIndex */_, model, cell in cell.assignment = model } let observer: Observable<[Assignment]> // For the widget version of this controller, only display the first N // items in a table-view that does not scroll. If more than N items exist // the user must tap the `More` view. For the non-widget view, show all // available items in a scrolling table-view. if isWidget { tableView.isScrollEnabled = false observer = viewModel .assignments .asObservable() .map({ Array($0.prefix(CPAssignmentWidgetVC.numberOfItemsToDisplay))}) } else { observer = viewModel .assignments .asObservable() } observer .distinctUntilChanged() .bind(to: tableView.rx.items(cellIdentifier: "AssessmentCell"), curriedArgument: curriedArgument) .disposed(by: disposeBag) // When something changes, update both the widget and the modal view as // needed. if isWidget { Observable .combineLatest(viewModel.fetching.asObservable(), viewModel.assignments.asObservable()) .subscribe(onNext: { [weak self] (isFetching, assignments) in self?.updateFooter(with: assignments, isFetching: isFetching) }) .disposed(by: disposeBag) } else { viewModel.fetching.asObservable() .distinctUntilChanged() .debounce(.milliseconds(500), scheduler: MainScheduler.instance) .subscribe(onNext: { isFetching in if isFetching { SVProgressHUD.show() } else { SVProgressHUD.dismiss() } }) .disposed(by: disposeBag) } // Select cell tableView .rx .itemSelected .subscribe(onNext: { [unowned self] indexPath in self.tableView.deselectRow(at: indexPath, animated: true) guard let cell = tableView.cellForRow(at: indexPath) as? AssessmentTableViewCell, let assignment = cell.assignment else { return } if cell.assignmentResult != nil { presentOptions(cell) } else { guard let patient = patient, patient.hasPermissionToModify(permissionType: .assessments) else { let title = NSLocalizedString("We're sorry!", comment: "Notification error title") let message = NSLocalizedString("You don't have permission to complete this survey", comment: "Notification error message") let dismiss = NSLocalizedString("Dismiss", comment: "Button title") LHDNotification.show(with: title, text: message, type: .error).setDismissButtonTitle(dismiss) return } presentAssignmentEntryForm(assignment) } }) .disposed(by: disposeBag) // Delete cell (remove associated backing model value) tableView .rx .itemDeleted .subscribe(onNext: { indexPath in self.viewModel.remove(at: indexPath.row) }) .disposed(by: disposeBag) // Display last cell tableView .rx .willDisplayCell .subscribe(onNext: { [unowned self] (/* cell */_, indexPath) in if indexPath.item + 1 == self.viewModel.numberOfItems { self.viewModel.loadMore() } }) .disposed(by: disposeBag) } }
Question: Why is the first row content being wiped out? Assuming no one can know the answer to this I have a follow-up question. How can I get the contents of the entire tableview to redraw themselves after a successful delete? I would have assumed the binding to the table-view on line 27 would handle this automatically when the view-model updates.