#박스오피스 PR # 박스오피스 step3 @July911 줄라이 안녕하세요! 코낄이, 리지 입니다. step3 PR 완성하여 올립니다! 매번 좋은 피드백 감사합니다. 이번에도 잘 부탁드립니다 ☺️ # 고민했던 점 ## 1️⃣ listCell과 customCell 저희는 Mordern Collection View를 구현하는 방법으로 `UICollectionViewListCell`을 사용하는 방법(iOS 14 타겟)을 선택했었습니다. 하지만 줄라이의 조언을 듣고 listCell을 사용하지 않는 구현을 경험해보지 않는 것이 아쉬워서, iOS 13 타겟으로 listCell을 사용하지 않고 다시 구현해보았습니다. <img src="https://i.imgur.com/sEQJ37Z.png" width=250> listCell로 구현하는 방식은 [Implementing Modern Collection Views](https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views) 에서 제공하는 샘플 코드 일부를 사용하였습니다. ### listCell을 사용한 구현에서 느낀 점 - Layout이 편했습니다. ``` swift private func createLayout() -> UICollectionViewLayout { let config = UICollectionLayoutListConfiguration(appearance: .plain) return UICollectionViewCompositionalLayout.list(using: config) } ``` 셀의 레이아웃을 따로 구현할 필요 없이 `UICollectionViewCompositionalLayout.list`만으로 가로 화면, 세로 화면에서 컬렉션뷰의 레이아웃이 리스트 형태로 완벽하게 구성되는 것을 화인할 수 있었습니다. - 코드를 이해하기가 어려웠습니다. ``` swift @available(iOS 14.0, *) extension UIConfigurationStateCustomKey { ... } @available(iOS 14.0, *) extension UICellConfigurationState { ... } @available(iOS 14.0, *) final class DailyMovieListCell: UICollectionViewListCell { override var configurationState: UICellConfigurationState { ... } ... ``` `UIConfigurationStateCustomKey`과 `UICellConfigurationState`, `DailyMovieListCell.configurationState`의 관계를 파악하기가 어려웠습니다. 또한 `UICollectionViewListCell`을 상속하는 클래스에서 구현하는 ``` swift override func updateConfiguration(using state: UICellConfigurationState) ``` 메서드는 직접 호출되지 않고 `setNeedsUpdateConfiguration()`를 통해 호출 요청을 보내는데, 이러한 부분도 추적하기가 어려웠습니다. listCell을 사용하기 위해서는 충분히 시간을 가지고 학습해야 할 것 같다고 생각했습니다. ## 2️⃣ snapshot을 찍어주는 주체 `NSDiffableDataSourceSnapshot` 공식문서를 확인해보니 Diffable data source가 snapshot을 사용해 collectionView에게 필요한 데이터를 제공한다는 것을 알 수 있었습니다. 처음엔 `collectionView`에 데이터를 받아서 띄우는 쪽으로 생각해서 `collectionView` 타입에 구현하였는데, 정확한 개념을 이해하니 MVC 관점에서 `collectionView`는 단순히 `View`를 담당하고 이 `View`에 데이터를 전달하는 즉, snapshot을 찍는 역할은 `viewController`에서 하는 것이 맞다고 생각하여 `viewController`에 구현하였습니다. ## 3️⃣ customCell로 구현시 AccessoryType 문제 `ListCell`로 구현했을 때는 `accessoryType`이 있어 쉽게 적용할 수가 있었는데,`customCell`로 구현하니 `accessoryType`을 찾지 못했습니다. 그래서 저희는 직접 imageView로 구현하였습니다. - **적용한 방법** `UIImage(systemName: "chevron.right")` 로 > 이미지를 넣은 후 색상을 변경하였고, `widthAnchor`, `heightAnchor`를 `mainStackView`의 배율로 크기를 지정하였습니다. ```swift= private var accessoryImageView = UIImageView() private func configureAccessoryImageView() { accessoryImageView.image = UIImage(systemName: "chevron.right")?.withTintColor(.systemGray2, renderingMode: .alwaysOriginal) accessoryImageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ accessoryImageView.trailingAnchor.constraint(equalTo: mainStackView.trailingAnchor), accessoryImageView.widthAnchor.constraint(equalTo: mainStackView.widthAnchor, multiplier: 0.025), accessoryImageView.heightAnchor.constraint(equalTo: mainStackView.heightAnchor, multiplier: 0.2) ]) } ``` ## 조언을 구하고 싶은 점 ### 1️⃣ customCell로 구현시 Autolayout 문제 - 줄라이의 피드백을 받고 `customCell`을 활용해보고자 했습니다. cell을 구성하는 UI는 크게 `mainStackView` - `movieRankStackView`, `movieListStackView`,`accessoryImageView` 로 구성하였고, `movieRankStackView`, `movieListStackView` 안에는 `label`을 두개씩 배치하였습니다. 그리고 필요한 제약을 걸어 autolayout을 잡았는데, 아래와 같은 문제가 발생하였습니다. - customCell로 구현시 문제된 화면 <img src="https://i.imgur.com/8YG5K4q.gif" width=250><img src="https://i.imgur.com/pMio0Mi.gif" width=250> - 저희가 생각한 문제의 원인은 UICollectionViewLayout을 잡는 곳에서 itemSize와 관련있다고 생각했습니다. 따라서 `.fractionalHeight`,`absolute`, `estimated`로 확인을 해봤는데, 답을 찾지 못하였습니다. - **시도한 방법** - `.fractionalHeight(x)`: x 값을 0.1 보다 높여주어 회전했을 때 cell크기가 유지되도록 하였으나 세로 화면일 때 cell크기가 예시화면보다 큰 문제가 발생 - `absolute(44.0)`, `estimated(44.0)` : 첨부한 첫 번째 화면과 같이 아래로 스크롤 했을 때, cell의 크기가 줄어들어 있거나 화면 회전시 화면을 벗어난 cell의 크기가 다른 cell보다 커지는 현상 발생 ```swift func createMovieListLayout() -> UICollectionViewLayout { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.1)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) } ``` 문제의 원인을 저희가 잘못 생각했을까요? 다른 어떤 방법을 시도해볼 수 있을까요? 🫠 ## 2️⃣ 동일한 파일 내에서 extension을 하는 기준 `DailyBoxOfficeViewController` 내에서 메서드를 다음과 같이 구현하였습니다. ``` swift final class DailyBoxOfficeViewController: UIViewController { @objc private func refreshData() private func updateDateToViewTitle() private func updateDateToEndPoint() // 서버에 데이터를 요청하고 받아온 데이터를 저장하는 기능 private func fetchDailyBoxOfficeData() } // dataSource를 통해 cell에 데이터를 전달하는 기능 extension DailyBoxOfficeViewController { private func setupDataSource() private func setupSnapshot() } // collectionView의 레이아웃을 생성하는 기능 extension DailyBoxOfficeViewController { private func createMovieListLayout() } ``` 뷰컨트롤러가 많은 기능을 가지기 때문에, 유사한 기능을 묶어 가독성을 개선하고자 위와 같이 extension을 사용하였는데, 적절한 기준으로 분류를 했는지 의문이 들었습니다. `fetchDailyBoxOfficeData`는 서버에 데이터를 요청하는 기능이고, `setupDataSource`과 `setupSnapshot`는 cell에 데이터를 전달하는 기능입니다. 따라서 두 기능을 별개로 분리하였지만, 사실 cell에 데이터를 전달하는 두 메서드는 `fetchDailyBoxOfficeData`내에서 호출됩니다. 이처럼 서로 다른 기능이지만 연관이 되어 있어 기능을 딱잘라 구분하기엔 어려움이 있었습니다. 이럴 때 기능을 구분하는 기준을 정하는 데 도움이 될만한 조언이 있을까요? :thinking: