들어가며
TableView와 CollectionView는 거의 뭐 운명공동체라고 봐도 무방 할 만큼 정말 많이 사용되는 녀석들입니다..!
그래서 우리는 개발을 하다보면 TV, CV의 내부 데이터들이 추가되고 삭제되어 뷰가 변경되는 일이 자주 발생하죠
오늘은 별 생각 없이 reloadData()만 사용하던 제 자신을 반성하며 언제, 어떤 메소드를 사용해야 가장 좋은 것인지!
TableView, CollectionView의 변화을 처리하는 방법에 대해서 알아보도록 하겠습니다!
제가 알아 볼 메소드들을 크게 분류하면 다음 3가지 입니다.
1. reloadData()
2. beginUpdates(), endUpdates() - table view
3. performBatchUpdates(_:completion:) - table view, collection view
reloadData()
apple developer는 reloadData()를 이렇게 설명하고 있습니다.
Reloads all of the data for the collection view.
: collection view/ table view의 모든 data를 reload한다.
Call this method when the data in your data source object changes or when you want to force the collection view to update its contents. When you call this method, the collection view discards any currently visible items and views and redisplays them. For efficiency, the collection view displays only the items and supplementary views that are visible after reloading the data. If the collection view’s size changes as a result of reloading the data, the collection view adjusts its scrolling offsets accordingly.
Do not call this method in the middle of animation blocks where items are being inserted or deleted. The methods used to insert and delete items automatically update the collection view’s contents.
: table view와 collection view의 reloadData()설명이 조금 상이한데 읽어보면 핵심적인 내용은 같습니다. 얘네는 일단 호출되면 table view와 collection view를 다시 그린다는 것. 그래서 만약 호출의 결과물로 table view와 collection view의 사이즈가 달라지면 알아서 스크롤 offset도 그에 맞춰 조정됩니다.
+ item을 추가하거나 삭제하는 중에 본 메서드를 사용하지 말라고도 써있네요 어차피 걔네가 다시 내용을 update해주니까 할 필요 없다고..
reloadData()는 그냥 한번에 다시 처음부터 그려버리니까 코드 작성시에 간단한 건 있어도 그만큼 비용이 크다는 단점이 있습니다.
하나의 데이터가 바뀌었을 뿐인데 그 하나의 데이터를 보여주기 위해서 바뀌지 않은 데이터들도 다시 그려주는 꼴이 되니까요.
그래서 reloadData()의 방식에는 다른 메서드들도 있습니다. reloadData()를 호출 했을 때 다시 그리게 되는 범위를 특정 범위로 한정 짓는 것이 차이점입니다. 뷰를 다시 그려야하는데 전부 다 다시 그릴 필요는 없을 때 사용하면 좋겠네요!
collection view, table view 모두, 부분 reload를 위해 존재하는 메서드들의 종류는 비슷합니다!
하지만 파라미터에서 약간 차이가 있는데요, reloadSections, reloadRows(Items) 모두 애니메이션을 동반하는데, tableView의 경우에는 애니메이션을 with: 에 .fade 와 같은 형식으로 지정이 가능하고, collecion view는 그런거 없이 default로 적용이 되는 것 같습니다!
beginUpdates(), endUpdates()
둘은 괄호처럼 하나의 set으로 쓰입니다. beginUpdates(), endUpdates()는 tableView에서만 사용 가능하기 때문에 tableView를 예로 들어보겠습니다!(얘보다 더 좋은게 뒤에 나오는데 그건 둘 다 사용 가능하니 걱정안하셔두대요)
우선, 아래의 메서드들은 collectionView, tableView 모두 사용가능한 메서드들입니다. 각각의 사용 용도에 따라 호출이 되겠죠?
// 1개 이상의 Row를 지정된 indexPath에 추가한다.
func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
// 1개 이상의 Section을 지정된 index에 추가한다.
func insertSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
// 1개 이상의 Row를 지정된 indexPath에서 제거한다.
func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
// 1개 이상의 Section을 지정된 index에서 제거한다.
func deleteSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
// 1개 이상의 Row를 리로딩한다.
func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
// 1개 이상의 Section을 리로딩 한다.
func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
방금 reloadData()에서 알아보았던 reloadSections도 보이네요! 위의 메서드들은 호출 되면 애니메이션을 동반하며 변경된 뷰에 맞게 delegate, datasource에 쿼리를 보내게 됩니다!
근데 만약 제가 저 메서드들을 하나씩 따로 호출하는게 아닌 경우..
예를 들어, "셀이 2개가 지워지면서 새로운 셀이 1개 추가되어야 한다." 라고 한다면? 메서드를 3개 호출하게 되겠네요? 애니메이션도 3개가 될 것이고 쿼리도 3개가 될 것이고..
바로 그런 상황에서 아래와 같은 형태로 beginUpdates(), endUpdates()를 활용할 수 있습니다:)
tableView.beginUpdates()
// code
tableView.endUpdates()
애플에서는 이 메서드를 아래와 같은 이유로 사용하기를 권장합니다.
The main reason for doing a batch update of changes to a table view is to avoid having the table animate unnecessarily.
: 테이블 뷰의 변화에 대해서 일괄 업데이트를 수행하는 주요 이유는 테이블 뷰의 불필요한 애니메이션을 피하기 위함이다.
어떻게 피한다는 걸까요?
begin과 end사이에 "셀이 2개가 지워지면서 새로운 셀이 1개 추가되어야 한다." 에서 필요한 메서드들을 넣는다면 쿼리가 들어가는 시점은 각 메서드 호출 시점이 아닌, endUpdates()이후가 됩니다. 만약 두 메소드 사이에 아무것도 호출하지 않는다 하더라도, 쿼리는 무조건 들어가게 됩니다! 쿼리를 보내는 시점이 일치되기 때문에 애니메이션도 더 깔끔하게 동작할 것이고 데이터가 꼬일 일도 없을 것이라는 장점이 있습니다.
"근데..왜 tableView에서만 사용할 수 있게 해주는데?"
performBatchUpdates(_:completion:)
그래서 이 친구가 나온걸까요? tableView에서만 사용 가능했던 begin/endUpdates()와 달리 이 메서드는 tableView, collectionView 둘 다 사용 가능합니다!
func performBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil)
begin/endUpdates()에서 달라진 점은..
애니매이션 블록을 지정하는 방법을 클로저 형태로 바꿔주었고, 애니매이션의 성공 여부에 따른 동작을 지정해 줄 수 있는 클로저 블록이 추가 되었습니다. begin/endUpdates()보다 활용할 수 있는 방법이 더 많아졌겠네요!
애플에서는 이렇게 설명하고 있습니다
You can use this method in cases where you want to make multiple changes to the collection view in one single animated operation, as opposed to in several separate animations. You might use this method to insert, delete, reload, or move cells or use it to change the layout parameters associated with one or more cells. Use the block passed in the updates parameter to specify all of the operations you want to perform.
: 한 번의 애니메이션으로 다중 작업을 처리해야 할 때 이 메서드를 사용하면 돼(beginUpdate()때랑 똑같죠?). 너는 하나 혹은 그 이상의 셀에 대한 삽입, 삭제, 리로드, 셀 이동이나 레이아웃 파라미터를 변경할 때 이 메서드를 사용하게 될거야. 블록을 사용해서 너가 수행하고자 하는 메서드를 명시해봐
If the collection view's layout is not up to date before you call this method, a reload may occur. To avoid problems, you should update your data model inside the updates block or ensure the layout is updated before you call performBatchUpdates(_:completion:).
: 주의사항이 있어. 만약에 너가 이 메서드를 사용하기 전에 너의 컬렉션뷰의 레이아웃이 최신 상태가 아니라면, 너는 문제를 피하기 위해서 너의 뷰를 강제로 리로드를 시키등가, 무튼 최신 상태임을 보장해주어야 해.
[중요]
Deletes are processed before inserts in batch operations. This means the indexes for the deletions are processed relative to the indexes of the collection view’s state before the batch operation, and the indexes for the insertions are processed relative to the indexes of the state after all the deletions in the batch operation.
: 삭제가 무조건 삽입보다 먼저 수행돼. 이 말은 너가 삽입 코드를 먼저 작성했더라도 삭제가 먼저 수행된다는 뜻이야. 그러니까 코드 작성 시 그걸 잘 고려해서 코드를 작성하길 바래.
오늘 알아본 내용은 여기까지입니다!
그냥 막연히 reloadData()만을 사용하기 보다는(본인 얘기) 상황과 목적을 고려하여 적절한 메서드를 사용하는 습관을 더욱 길러야 겠다는 생각이 드네요 :)
글에 오류가 있다면 댓글작성 꼭! 부탁드리겠습니다. 감사합니다 :)