박스오피스를 일별, 주중/주말 순위 로 구분해서 볼 수 있는 앱 입니다.
MVVM + C
- MVVM 패턴, Coordinator
UI 구현
- 코드 베이스 UI
- 오토레이아웃
- CollectionView Compositional Layout
- Custom Modal
비동기처리
- RxSwift
이미지 캐싱
- URLCache
- Dictionary Cache
일별 박스오피스 화면 | 주간/주말 박스오피스 화면 |
---|---|
- FlowCoordinator
- HomeFlowCoordinator
- HomeView
- ViewModel
- View
- MovieDetailView
- ViewModel
- View
- MovieCellData
- MovieDetailData
- SearchDailyBoxOfficeUseCase
- SearchWeekDaysBoxOfficeUseCase
- SearchWeekEndBoxOfficeUseCase
- SearchMovieDetailBoxOfficeUseCase
- DailyBoxOfficeListResponseDTO
- WeeklyBoxOfficeListResponseDTO
- MovieDetailInfoResponseDTO
- MoviePosterResponseDTO
- MovieRepository
- PosterImageRepository
보기모드에 따라 Layout을 다르게 적용 해주었습니다.
일별 박스오피스 레이아웃 생성 메서드
func createDailyLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(
top: 0,
leading: 10,
bottom: 0,
trailing: 10
)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.25)
)
let group = NSCollectionLayoutGroup.vertical(
layoutSize: groupSize,
subitems: [item]
)
let section = NSCollectionLayoutSection(group: group)
let headerFooterSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(60)
)
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: "headerView",
alignment: .top
)
section.boundarySupplementaryItems = [sectionHeader]
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
주중/ 주말 박스오피스 레이아웃 생성 메서드
func createWeeklyLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(
top: 0,
leading: 10,
bottom: 0,
trailing: 10
)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.45),
heightDimension: .fractionalHeight(0.45)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitems: [item]
)
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
let headerFooterSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(50)
)
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: "headerView",
alignment: .top
)
section.boundarySupplementaryItems = [sectionHeader]
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
보기모드 변경 버튼(상단 버튼)
,캘린더 버튼
,출연진 더보기 버튼
을 Custom Modal 형식으로 구현 했습니다.- Presentation Controller 에서 화면 전환 시 dimmingView 처리와 UIGesture를 통해 화면 dismiss를 구현 했습니다.
보기모드 변경 | 날짜 선택 | 출연진 보기 |
---|---|---|
- 일본, 미국 에서 사용할 수 있도록 Localization을 적용 했습니다.
일본 지역화 | 미국 지역화 |
---|---|
- 라이트 모드와 다크 모드에서 색감 차이가 없도록
Semantic Color
를 사용했습니다.
일별 박스오피스 화면 | 보기모드 변경 모달뷰 | 주간/주말 박스오피스 화면 | 캘린더뷰 |
---|---|---|---|
- 가로모드와 세로모드에 대응할 수 있도록 Layout을 설정 해주었습니다.
- 기기 회전 이벤트에 Notification을 적용하여 layout을 변경해주었습니다.
일별 박스오피스 화면 | 주간/주말 박스오피스 화면 |
---|---|
- 시력이 좋지 않은 사용자를 위해 Dynamic Type을 적용 했습니다.
- 시각 장애인 사용자를 위해 Voice Over를 적용 했습니다.
일별 박스오피스 화면 | 주간/주말 박스오피스 화면 |
---|---|
- 네트워킹에 대한 Unit Test
- UseCase에 대한 Unit Test
- ViewModel에 대한 Unit Test
DiffableDataSource 에서 기존에는 item identifier로 모델타입을 넣어 주었습니다.
하지만 참고해본 `개발자 포럼`에서 값타입의 모델을 사용 할 경우, uuid값을 item identifier
로 넣어주는 것이 데이터 관리에 용이하고, 중복으로 인한 크래쉬를 방지하기 쉽다고 하여 적용 해보았습니다.
func createDailyCellRegistration() -> UICollectionView.CellRegistration<ListCell, String> {
let cellRegistration = UICollectionView.CellRegistration<ListCell, String> { (cell, _, id) in
let item = self.viewModel.dailyBoxOffices.value.filter { $0.uuid == id }
self.setupCell(with: item[0], at: cell, id: id)
}
return cellRegistration
}
HomeCollectionView의 Cell Registration 부분에서 id 값에 해당하는 모델을 찾아
setupCell 메서드에 적용 해주었습니다.
- 이미지 요청의 반환속도 때문에 전체 Cell 업데이트 속도의 지연이 있었습니다.
- 이를 해결하기 위해 이미지는 PosterImageRepository에서 따로 받아오도록 작업하고, 나머지 셀 정보들은 바로 업데이트가 되도록 수정 했습니다.
- 셀의 Text 정보들이 미리 업로드 된 후, 이미지를 받아왔을 때 또 한번 업데이트를 해주어야 하는 것에서 문제가 생겼습니다.
- 셀을 업데이트 해주는 방법에는 snapshot의 reloadItem() 과 reconfigureItems() 메서드가 있습니다.
NSDiffableDataSourceSnapshot 에 존재하는 reloadItem() 메서드와 reconfigureItems() 메서드의 차이점에 대해 알아봤습니다.
-
reloadItem() 메서드
- 다른 셀 타입으로 업데이트 해야할 때 사용합니다.
-
reconfigureItems() 메서드
- 같은 셀 타입에, 정보만 업데이트 할 때 사용합니다.
Make blazing fast lists and collection views WWDC
를 참고해본 결과, reconfigureItem() 메서드의 효율성이 더 좋다고 판단하여 이를 적용 해주었습니다.
로딩 속도 개선 전 | 로딩 속도 개선 후 |
---|---|
로딩속도 약 3초 |
로딩 속도 1초 이내 |
선택영역의 카테고리들이 MovieDetailViewController로 들어갔다가 나올 때마다 증가하는 문제가 발생했습니다.
원인 분석 결과, RxSwift의 사용에서 문제가 있었습니다.
private func bindData() {
viewModel.movieDetailData
.observe(on: MainScheduler.instance)
.subscribe { [weak self] movieDetailData in
self?.movieMainInfoView.configure(
with: movieDetailData,
rating: "",
repository: (self?.posterImageRepository)!
)
self?.movieSubInfoView.configure(with: movieDetailData)
self?.activityIndicator.stopAnimating()
} onError: { [weak self] error in
guard let self = self else { return }
DefaultAlertBuilder(message: error.localizedDescription)
.setButton()
.showAlert(on: self)
}
.disposed(by: disposeBag)
}
위 코드로 변경하기 전에 [weak self] 처리를 해주지 않았었는데, 이 때문에 참조가 남아있어 allocation이 계속 증가 한 것으로 판단되었습니다. 위 코드처럼 모두 [weak self] 로 처리해준 결과, allocation이 쌓이는 현상이 없어졌습니다.