* 진행 코드는 https://github.com/jeongju9216/Jetflix에서 볼 수 있고, PR에서 에피소드 단위로 코드를 확인할 수 있습니다.
서론
이번 포스팅에서는 리스트 레이아웃을 개선한 내용을 작성하겠습니다.
기존의 UITableView + CollectionView FlowLayout 구조를 CollectionView CompositionalLayout 단일 구조로 변경했습니다.
고수님들께는 간단한 작업일 수 있지만,
저는 이 작은 작업에서도 왜 오래된 코드를 최신 코드로 바꾸기 어려운건지 느낄 수 있었습니다 ㅎㅎ;
개선 내용은 아래와 같습니다.
- CollectionView CompositionalLayout 적용
- UICollectionViewDiffableDataSource 적용
- DefaultCollectionViewCell 적용
(참고로 이번 포스팅은 CompositionalLayout, DiffableDataSource 개념에 대해서는 다루지 않습니다.
WWDC를 보고 따로 포스팅을 작성할 계획입니다.)
1.
Jetflix 레이아웃에는 가로 스크롤이 존재합니다.
기존에는 UITableViewCell로 CollectionView를 넣는 방식으로 UITableView -> CollectionView -> CollectionViewCell 구조입니다. 즉, 한 개의 View가 추가되니 상대적으로 비효율적입니다.
이를 Compositional Layout으로 변경하면 UICollectionView 하나로 가로 스크롤을 구현할 수 있습니다.
NSCollectionLayoutGroup.horizontal(layoutSize:subitems:)
위 메서드를 이용해 가로 스크롤을 구현했고, UITableView를 삭제할 수 있었습니다.
가로 스크롤이 horizontal이니 세로 스크롤은 vertical이겠죠? ㅎㅎ
이렇게 개선된 부분도 있었지만, 코드가 추가된 부분도 있었습니다.
바로 최상단의 HeaderView 입니다.
기존에는 이 View를 tableHeaderView에 대입하는 것만으로 구현이 가능했습니다.
하지만 Compositional Layout의 header는 모든 섹션에 header가 들어가서 최상단에만 들어가지 않았습니다.
그래서 "header" 섹션을 따로 추가해서 Header View Cell을 반환해주는 것으로 수정해야 했고,
UIView를 UICollectionViewCell로 바꿔서 적용하는 것으로 수정했습니다.
이 과정에서 이렇게 작은 앱에서도 기능 하나를 수정할 때 파생되는 문제가 있는데,
현업의 큰 앱에서는 리팩토링이라는 것이 큰 도전이겠다 라는 생각이 들었습니다.
최소 지원 버전을 높이고, 레거시 코드를 꾸준히 없애는 작업이 대단한 일이라는 것을 느꼈네요.
2.
Compositonal Layout과 함께 UICollectionViewDiffableDataSource도 적용했습니다.
UICollectionViewDiffableDataSource는 기존 DataSource에 Hashable이 적용되어 탐색 속도가 빠릅니다.
UICollectionViewDiffableDataSource<Sections, Content>
Section을 나누는 Enum을 정의하고, Section에 들어가는 product 타입을 넣습니다.
셀 하나하나가 Content 타입이므로 Content가 들어가는 것입니다.
product 타입은 반드시 Hashable을 채택해야 합니다.
DiffableDataSource는 UICollectionViewDiffableDataSource<Sections, Content>이 타입이 되므로 굉장히 깁니다.
이럴 때는 typealias를 쓰면 편하게 사용할 수 있습니다.
private typealias DataSource = UICollectionViewDiffableDataSource<Sections, Content>
private typealias SnapShot = NSDiffableDataSourceSnapshot<Sections, Content>
enum Sections: Int, CaseIterable {
case header, TrendingMovies, TrendingTv, Popular, Upcoming, TopRated
var title: String {
switch self {
case .header: return ""
case .TrendingMovies: return "Trending Movies"
case .TrendingTv: return "Trending TV"
case .Popular: return "Popular"
case .Upcoming: return "Upcoming Movies"
case .TopRated: return "Top rated"
}
}
}
Jetflix의 Home 화면에는 header를 포함해서 6개의 Section이 있습니다.
Section Enum을 구현하면서 따로 관리하던 Title을 Enum에서 관리하도록 개선하여 개발자 실수를 줄일 수 있었습니다.
3.
마지막으로 한 가지 개선을 추가로 했습니다.
guard let cell = tableView.dequeueReusableCell(withIdentifier: CollectionViewTableViewCell.identifier, for: indexPath) as? CollectionViewTableViewCell else {
return UITableViewCell()
}
cell 생성에 실패했을 때 UITableViewCell 혹은 UICollectionViewCell을 반환하는 코드를 쉽게 볼 수 있습니다.
근데 위 코드를 수행하면 런타임에러가 발생한다는걸 아시나요?
Thread 1: "the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier - cells must be retrieved by calling -dequeueReusableCellWithReuseIdentifier:forIndexPath:"
실제로 실행해보면 위 에러와 함께 크래시가 발생합니다.
그래서 DefaultCollectionViewCell 클래스를 생성해서 반환해주어야 합니다.
저는 그냥 기본 XIB를 가진 기본 Cell을 하나 만들어서 반환해주도록 구현했습니다.
let defaultCell = collectionView.dequeueReusableCell(withReuseIdentifier: DefaultCollectionViewCell.identifier, for: indexPath)
guard let cell = ... else {
return defaultCell
}
(당연히 DefaultCell 클래스도 CollectionView에 register 시켜야 합니다.)
마무리
이번 클론 코딩을 통해 클론 코딩으로도 많은걸 배울 수 있다는 것을 알았습니다.
그래서 다양한 앱을 따라 만들어보면서 복잡한 레이아웃, View, 애니메이션을 공부해보려고 합니다.
하나의 앱에 이런 클론 코딩을 구현하고, 버튼을 누르면 레이아웃이 바뀌도록 구현하면 재밌을 거 같네요.
View 재사용 공부도 될 거 같고... ㅎㅎ
여러 프로젝트를 방치하는게 아니라 꾸준히 개선하면서 성장해야겠습니다.
취업 준비도 함께요...ㅠㅠ
감사합니다.
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.