10주차 과제 (11)
Datasource에서 DiffableDatasource로 변경하면서 생긴 Exception이 있다.
- 검색 후 cell 클릭 시 발생하는 에러
- WishList에서 셀 삭제 시 에러 발생
이렇게 2개의 문제가 발생했다.
1. 첫번째 문제
1
*** Assertion failure in -[_UIDiffableDataSourceUpdate initWithIdentifiers:sectionIdentifiers:action:desinationIdentifier:relativePosition:destinationIsSection:], _UIDiffableDataSourceHelpers.m:504
그냥 셀을 클릭하면 이런 에러문구가 나온다.
혹시나해서 Coredata에 저장이되면서의 문제일까 싶어 주석을 달았지만 에러는 그대로 발생하는걸로 확인이 되었다.
확인해본결과 viewDiddisappear에서 발생하는것으로 보인다.
bind함수에서 문제 발생으로 추정
1
2
3
4
5
6
7
8
9
10
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
recentVM.getDocument()
recentVM.$recentDocument
.receive(on: DispatchQueue.main)
.sink { [weak self] data in
self?.collectionConfigureSnapshot()
}.store(in: &cancellables)
checkEmpty()
}
이렇게 바꿔주니 해결이 되었다.
계속 에러가 프린트될때 collectionview쪽이 아닌 검색한 결과에대해 나온것을 보고 혹시나 해서 컬렉션뷰만 리로드 하게끔 했는데, 문제가 해결 되었다.
아무래도, 같은 정보가 그대로 있는상태 에서 그대로 또 적용을 하려다보니 duplicate가 발생한것으로 생각이 된다.
viewDidDisappear
를 내가 VC LifeCycle을 고려해서 작성했던 이유는 collectioView를 화면에 가려졌을때 미리 갱신을 시키기 위함이었는데,
diffable 적용하기 전까진 bind를 해도 상관이 없었으나, diffable의 경우 Hashable
이 가장 중요했는데, 내가 그걸 망각하고 있었다.
귀찮아서 bind로 퉁치려고 했다가 예외가 발생했던 것.
해결 완료.
그러면 searchVM.document
에 대해서 removeAll()
을 해서 다시 초기화 하고 bind를 트리거하면 되지 않을까? 해서 해봤는데
1
2
3
4
5
6
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
searchVM.document.removeAll()
bind()
checkEmpty()
}
바로 여기서
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailVC = DetailViewController()
// DetailVC에 전달
searchVM.$document
.map{ document in
return document[indexPath.row] // out of range error 발생
}
.eraseToAnyPublisher()
.receive(on: DispatchQueue.main)
.sink { [weak detailVC] document in
let imageURL = URL(string: document.thumbnail)
detailVC?.titleView.titleLabel.text = document.title
detailVC?.titleView.authorLabel.text = document.authors.joined()
detailVC?.imageView.imageView.kf.setImage(with: imageURL)
detailVC?.imageView.priceLabel.text = document.price.stringValue
detailVC?.bodyView.bodyLabel.text = document.contents
detailVC?.wishSubject.send(document)
}.store(in: &detailVC.cancellables)
// CoreData에 등록
searchVM.$document
.map{ document in
if !document.isEmpty {
return document[indexPath.row]
} else {
return document[0]
}
}
.eraseToAnyPublisher()
.sink(receiveValue: { [unowned self] document in
recentVM.saveDataToRecent(data: document)
}).cancel()
detailVC.modalPresentationStyle = .fullScreen
present(detailVC, animated: true)
}
out of range가 발생한다. 아마 화면이 사라지면서 올라오다보니. 미리 데이터를 다 지워버리게 되는꼴 그래서 document가 empty 상태가 되어버려서 문제가 발생한것이다.
이것저것 시도해봤지만 결론은
그냥 의도했던대로 CollectionView 갱신만 하자
오히려 viewwillappear에 해두니 collectionview에 셀이 추가되는것도 볼 수 있어서 더 좋은듯하다.
viewwillappear에 적용 한 사진.
2. 두번째 문제
1
2
*** Assertion failure in -[UITableView deleteRowsAtIndexPaths:withRowAnimation:], UITableView.m:8630
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView must be updated via the UITableViewDiffableDataSource APIs when acting as the UITableView's dataSource: please do not call mutation APIs directly on UITableView.
이건 전체삭제에서는 문제가 없지만 부분삭제에서 발생한다.
Stackoverflow를 읽다가
직접적인 해답은 없었으나 힌트를 얻어서 한번 해보려고한다.
그리고 Youtube 를 보는데 snapshot을 VC에 적용해서 하길래 역시 이부분이 문제인 것 같다고 확신이 들었다.
우선 snapshot을 VC로 빼주기로 결정 var snapshot: NSDiffableDataSourceSnapshot<DiffableSectionModel, WishListModel>?
그리고 snapshot 함수도 변경해준다.
기존에는 지역변수였다면, vc전체에서 쓰는 전역변수로 바꿨다는게 가장 큰 의미.
1
2
3
4
5
6
7
8
9
10
func configureSnapshot() {
snapshot = NSDiffableDataSourceSnapshot<DiffableSectionModel, WishListModel>()
snapshot?.deleteAllItems()
snapshot?.appendSections([.wish])
snapshot?.appendItems(wishVM.wishDocument)
wishTableDatasource?.apply(snapshot!,animatingDifferences: true)
}
뭔가 직접적으로 tableview에 접근하는건 tableView.deleteRows(at: [indexPath], with: .fade)
이거였는데,
지워버렸다.
처음에는 어딘지 고민을 했는데, 문제를 해결하고 보니 저부분에서 문제가 발생했을것으로 짐작하긴 했는데, 역시나였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteButton = UIContextualAction(style: .normal, title: "삭제") { (UIContextualAction, UIView, success: @escaping (Bool) -> Void) in
let alert = UIAlertController(title: "삭제하기", message: "정말 삭제하실 건가요?", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .destructive, handler: { [unowned self] _ in
wishVM.deleteSelectedData(selectedCell: wishVM.wishDocument[indexPath.row])
wishVM.wishDocument.remove(at: indexPath.row)
})
let cancel = UIAlertAction(title: "취소", style: .default)
alert.addAction(ok)
alert.addAction(cancel)
self.present(alert,animated: false)
success(true)
}
deleteButton.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [deleteButton])
}
확인 완료
snapshot을 적용하지 않는 이유는 combine을 사용해서
1
2
3
4
5
wishVM.$wishDocument
.receive(on: DispatchQueue.main)
.sink { [unowned self] _ in
configureSnapshot()
}.store(in: &cancellables)
여기서 값이 변하면 바로 snapshot을 적용하기 떄문. 이게바로 Combine의 편리함. 쓰면 쓸수록 매력적인 녀석이다.
생각해보니 굳이 안뺐어도 크게 지장은 없을것 같다. 하지만 나중에 또 적용할지 모르는 일이 생길 수 있기에 빼둔다.
이번에 이렇게 적용해보면서 디퍼블에대해 여러 에러도 접해보고 많이 배우게 되었다.
역시 뭐든 직접 부딪히면서 에러를 수정해야 많이 배우고 느낀다. 디퍼블 이번에 처음 사용해봤지만, 아주 기본적인건 이제 할 수 있을것같다.
아마 다듬어도 layout warning 정도만 해결하면 될 듯하다. (디자인 쓰레기)
Combine + DiffableDatasource 조합은 진짜 제대로 이해하고 사용하면 엄청 편할것같다.
진짜 끝.