---
# System prepended metadata

title: 박스오피스
tags: [Mary, Jiseong, group_8, idinaloq]

---

### 규칙
- 월, 목: 오후 6시부터 시작
- 화, 수, 금: 오전 11시부터 시작
- 일정 있으면 사전에 연락하기

### 스크럼
- 특이사항, 오늘의 할 일 작성하기

### 프로젝트 규칙
- 네이밍 준수하기(가이드 라인)
- 커밋 메시지 규칙 준수하기

### 브랜치 이름 규칙
- 공동 작업 브랜치: step0, step1 ... 
- 테스트 작업 브랜치: step0_idinaloq_test1, 2 ...

### 커밋 메시지 규칙
- 카르마 스타일 컨벤션 커밋메시지 사용
- ✨Feat: 기능 추가
- ♻️Refactor: 기능과 관련된 개선/전면수정
- 🩹Chore: 네이밍, 컨벤션 등 수정
- 🐛Fix: 오류 수정
- 📝Docs: 문서 추가 및 수정
- ✅Test: 테스트 추가
- 커밋 단위: 기능 단위, chore 묶어서 커밋

### 네이밍 컨벤션
- [API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/) 참고

## 일일 스크럼

### 🙌 07/24 (월)
- 특이사항
    - idinaloq: 내일부턴 조금 늦게 일어나겠습니다 👍
    - Mary: collection view 공식문서에 글이 너무 많아요...졸립니다... 예습 화이팅...😭
- 오늘 할 일
    - [x] 활동학습 Collection View 예습, 수업 참여하기
    - [ ] STEP1 PR (가능하면?)

### 🙌 07/25 (화)
- 특이사항
    - idinaloq: 오늘 컨디션 최고 😊
    - Mary: 저도 푹 쉬어서 컨디션 너무 좋습니다!!! 😆
- 오늘 할 일
    - [x] STEP1 PR
    - [ ] PR답변 빨리온다면 STEP2 맛보기

### 🙌 07/26 (수)
- 특이사항
    - idinaloq: 졸려요 😩
    - Mary: 컨디션 좋습니다! URLSession 문서 읽어봤는데 어렵네요... 오늘 열심히 공부해야할 것 같습니다 🥹
- 오늘 할 일
    - [x] STEP1 피드백 기반 수정
    - [ ] STEP2 PR

### 🙌 07/27 (목)
- 특이사항
    - idinaloq: 잘 쉬다왔습니다 😞
    - Mary: 저녁을 많이 먹었더니 배불러요ㅋㅋㅋㅋㅋ 열심히 머리 굴리면서 소화시키겠습니다ㅎㅎㅎ
- 오늘 할 일
    - [x] STEP2 구현

### 🙌 07/28 (금)
- 특이사항
    - idinaloq: 주말에 쉴 수 있을까요??????
    - Mary: 쉴 수 있도록 해보시죠 😂
- 오늘 할 일
    - [x] README 작성
    - [x] STEP2 PR

### 🙌 08/01 (화)
- 특이사항
    - idinaloq: 컨디션이 좋아요 👍
    - Mary: 웨일과 다산 + 코비도 만났습니다! 이디나로크도 내일 시간 되시면 대전 오세요😎
- 오늘 할 일
    - [x] STEP2 코멘트 달기!!

### 🙌 08/03 (목)
- 특이사항
    - idinaloq: 열심히 달리겠습니다 🐎
    - Mary: 크으 저도 열심히 달리겠습니다 화이탱!!!!! 🏇
- 오늘 할 일
    - [ ] STEP3 UI 구현

### 🙌 08/04 (금)
- 특이사항
    - idinaloq: 
    - Mary: 왜 벌써 금요일인거죠!!!!😭 오늘도 열심히 달려보겠습니다
- 오늘 할 일
    - [ ] STEP3 

# PR
박스오피스 [Step1] Mary, idinaloq


## 🔍 고민되었던 점

### 1. Nested Data Parsing
- 제공된 json 파일을 확인해보니 데이터가 여러번 중첩되어있는 형태였습니다.
```json
{
  "boxOfficeResult": {
    "boxofficeType": "일별 박스오피스",
    "showRange": "20220105~20220105",
    "dailyBoxOfficeList": [
      {
        "rnum": "1",
        "rank": "1",
        ...
      },
      {
        "rnum": "1",
        "rank": "1",
        ...
      },
      ...
    ]
  }
}          
```
- [공식문서](https://developer.apple.com/documentation/foundation/archives_and_serialization/using_json_with_custom_types#3540681)를 참고하여 다음과 같이 여러개의 DTO타입을 만들어주어 해결하였습니다.
```swift
struct BoxOffice: Decodable {
    let boxOfficeResult: DailyBoxOffice
}

struct DailyBoxOffice: Decodable {
    let boxOfficeType: String
    let showRange: String
    let dailyBoxOfficeList: [MovieInformation]
    ...
}

struct MovieInformation: Decodable {
    let rowNumber: String
    let rank: String
    let rankChangeValue: String
    ...
}
```

### 2. 실패하는 Test case
- 성공하는 테스트 케이스가 아닌, 실패하는 케이스에 대해서도 다음과 같이 작성을 했습니다.
```swift
func test_boxofficesample프로퍼티와_DailyBoxOffice프로퍼티가다르면_파싱에실패한다() { 
    let test = try? JSONDecoder().decode(BoxOffice.self, from: dataAsset)

    ...

    //then
    XCTAssertNil(test)
}
```


## 🙏 조언을 얻고 싶은 부분

### 1. JSON 파싱 테스트
- 현재 테스트메서드에서 파싱한 값들이 임시로 에셋에 넣어놓은 `JSON`파일의 내용과 같은지 테스트 하는 항목이 다음과 같이 존재합니다.
```swift
func test_boxofficesample파일을_DailyBoxOffice인스턴스에디코딩했을때_값이정상적으로추가된다() {
    ...
    
    XCTAssertEqual(boxOffice.boxOfficeResult.boxOfficeType, "일별 박스오피스")
    XCTAssertEqual(boxOffice.boxOfficeResult.showRange, "20220105~20220105")
    XCTAssertEqual(firstDailyBoxOffice.rowNumber, "1")
    XCTAssertEqual(firstDailyBoxOffice.rank, "1")
    XCTAssertEqual(firstDailyBoxOffice.rankChangeValue, "0")
    XCTAssertEqual(firstDailyBoxOffice.rankOldAndNew, "NEW")
    XCTAssertEqual(firstDailyBoxOffice.movieCode, "20199882")
    XCTAssertEqual(firstDailyBoxOffice.movieName, "경관의 피")
    XCTAssertEqual(firstDailyBoxOffice.openDate, "2022-01-05")
    XCTAssertEqual(firstDailyBoxOffice.salesAmount, "584559330")
    XCTAssertEqual(firstDailyBoxOffice.salesShare, "34.2")
    XCTAssertEqual(firstDailyBoxOffice.salesChangeValue, "584559330")
    XCTAssertEqual(firstDailyBoxOffice.salesChangeRatio, "100")
    XCTAssertEqual(firstDailyBoxOffice.salesAccumulate, "631402330")
    XCTAssertEqual(firstDailyBoxOffice.audienceCount, "64050")
    XCTAssertEqual(firstDailyBoxOffice.audienceChangeValue, "64050")
    XCTAssertEqual(firstDailyBoxOffice.audienceChangeRatio, "100")
    XCTAssertEqual(firstDailyBoxOffice.audienceAccumulate, "69228")
    XCTAssertEqual(firstDailyBoxOffice.screenCount, "1171")
    XCTAssertEqual(firstDailyBoxOffice.showCount, "4416")
}
```
해당 부분을 줄일 수 없을지에 대해 생각했고, 에셋에 등록되어있는 테스트용 `json`파일에서 첫 번째 블록만 가져와서 비교하면 될 것 같았습니다. 하지만 첫 번째 블록을 가져오는 것과, 원하는 내용을 고르는 부분이 잘 되지 않았습니다.
하나씩 테스트 하는것이 아닌, 한 번에 테스트를 하는 다른 방법이 있는지 알고 싶습니다.


# PR STEP2
박스오피스 [Step2] Mary, idinaloq

## 🔍 고민되었던 점
### 1. App Transport Security Settings
- App Transport Security Settings(ATS)는 iOS 9버전부터 도입된 사양으로, 보안에 취약한 네트워크의 연결을 차단시키기 때문에 영화진흥위원회의 HTTP로 접근을 할 수 없는 문제가 있었고, 다음과 같은 에러메세지를 출력했습니다.
![](https://hackmd.io/_uploads/Hk5ztybsh.png =300x)

- 해당 부분은 `plist`에서 `ATS`를 다음과같이 추가했고, 정상적으로 데이터를 받아올 수 있었습니다.
![](https://hackmd.io/_uploads/r1umY1Wjn.png)

## 🙏 조언을 얻고 싶은 부분
### 1. APIConstants의 재사용성
- 현재 상태에서는 `APIConstants` 인스턴스를 생성하고 사용할 때, `scheme`, `host`, `path`, `key`, `value`등과 같은 영화진흥위원회와 관련된 값이 이미 설정이 되어있는 상태 입니다.
만약 재사용성을 고려한다면,  `APIConstants` 인스턴스를 만들어서 사용하는 경우 값을 입력해서 다른 API에서도 사용이 가능하도록 구현을 하는게 좋은지, 만약 그렇게 한다면 `enum`을 사용해서 각각의 API와 관련된 값들을 모아두는게 좋은지 알고 싶습니다.
```swift 
struct APIConstants {
    private let baseURL: URLComponents = {
        var components = URLComponents()
        let key = URLQueryItem(name: QueryItem.key, value: QueryItem.value)
        
        components.scheme = Components.scheme
        components.host = Components.host
        components.path = Components.path
        components.queryItems = [key]
        
        return components
    }()
    ...
}
extension APIConstants {
    enum QueryItem {
        ...
        static let key: String = "key"
        static let value: String = "c824c74a1ff9ed62089a9a0bcc0d3211"
    }
    enum Components {
        static let scheme: String = "http"
        static let host: String = "www.kobis.or.kr"
        static let path: String = "/kobisopenapi/webservice/rest"
    }
}
```

### 2. enum에서 연산프로퍼티로 타입을 할당하기
- **일일 박스오피스**와 **영화 상세정보**를 서버에 요청할 때, `MovieService`타입의 `fetchData`에 `serviceType`만 지정해주면 공통으로 사용할 수 있도록 구현하고 싶었습니다. 
```swift
struct MovieService {
    func fetchData(serviceType: ServiceType) {
        ...
    }
}
```
- 따라서`decode(_:from:)`메서드의 매개변수인 type을 할당해주기 위해`serviceType`에 연산프로퍼티를 추가해주었습니다.
- 또한 `serviceType`에 따라 `type`의 타입(`BoxOffice` or `DetailInformation`)이 변경되므로 타입은 `Any`로 지정해주었습니다.
```swift
enum ServiceType {
    ...
    var type: Any {
        switch self {
        case .dailyBoxOffice:
            return BoxOffice.self
        case .movieDetailInformation:
            return DetailInformation.self
        }
    }
    ...
}
```
- 하지만 fetchData 메서드 내에서 `serviceType.type`을 활용하려고하면 다음과 같은 오류가 발생하였습니다.
``` swift
...
let task = URLSession.shared.dataTask(with: url) { data, response, error in {
    ...
    if let data = data,
       let decodedData = try? JSONDecoder().decode(serviceType.type, from: data) {
        print(decodedData)
    }
}
...
```
![](https://hackmd.io/_uploads/BJf5le-o2.png =500x)
- 해당 오류는 decode 메서드가 Decodable을 준수하는 T타입을 받거나 추론할 수 있어야하는데, `Any` 타입은 추론이 불가능하기 때문에 생기는 문제라고 생각했습니다.
    ``` swift
    // decode(_:from:)
    func decode<T>(
        _ type: T.Type,
        from data: Data
    ) throws -> T where T : Decodable
    ```
- 결국 해당 문제를 해결하지 못하고, switch 문을 통해 분기처리를 해주었는데, 다른 방법이 있을지 지성의 의견을 듣고싶습니다! 🙇‍♀️


# PR STEP3
박스오피스 [Step3] Mary, idinaloq

@yim2627

## 🔍 고민되었던 점
### 1.UIActivityIndicatorView
- 예전에 `tableViewCell`을 사용할 때 `accessory`타입의 `.idsclosureIndicator`를 활용해서 각각의 셀에 `>`모양을 표시했습니다. 하지만 `UICollectionViewCell`은 해당 기능이 없었고, 검색해 본 결과 [UICollectionViewListCell](https://developer.apple.com/documentation/uikit/uicollectionviewlistcell)이 있었습니다. 하지만 이는 **iOS14**부터 지원을 하기 때문에 저희가 처음에 만들어진 프로젝트의 **iOS13**보다 높아서 사용할 수 없었습니다. 

- 처음에는 기존 프로젝트 설정 그대로 가려고 했지만, 프로젝트에서 요구사항에는 iOS버전에 대한 이야기가 없었습니다. 찾아본 결과 공식페이지에 [iOS점유율](https://developer.apple.com/kr/support/app-store/)을 확인하는 곳이 있었고 아이폰의 81%가 **iOS16**을 사용하고 있다는 통계를 찾았고 이것이 저희가 버전을 수정하려는 이유가 될 수 있다고 생각했기 때문에 `UICollectionViewCell`의 [UICellAccessory](https://developer.apple.com/documentation/uikit/uicellaccessory) `.discatorIndicator`를 사용해서 `indicator`를 표시해 주도록 다음과 같이 코드를 작성했습니다.
```swift
private func configureUI() {
    ...
    self.accessories = [.disclosureIndicator()]
}
```


## 🧨 트러블 슈팅
### 1. Label의 text가 cell의 윗쪽으로 쏠려서 나타나는 문제
⚠️ 문제점
- cell의 AutoLayout을 잡아주었는데도 불구하고 text가 윗쪽으로 쏠려서 나타나는 문제점이 있었습니다.
![](https://hackmd.io/_uploads/SknB0jqoh.png =200x)

✅ 해결방법
- 해당 현상은 `UICollectionViewFlowLayout`의 `minimumLineSpacing`을 설정해주지 않아서 생기는 문제였습니다. 현재 cell의 윗쪽 부분에 CALayer를 만들어서 구분선을 표시해주고 있으며, `minimumLineSpacing`은 default값인 10으로 설정되어있어, 결과적으로 셀이 올라간 것 처럼 보였습니다.
- `UICollectionViewFlowLayout`의 `minimumLineSpacing`을 0으로 설정해줌으로써 문제를 해결하였습니다.

### 2. cell 재사용으로 인한 text 색상 문제
⚠️ 문제점
- 영화 순위 등락을 표시하는 `rankChangeValueLabel`은 등락에 따라 텍스트의 색상을 변경해주었습니다. 첫 실행화면은 정상적으로 보여지지만, 화면을 아래로 드래그하여 새로고침을 하거나, collectionView를 아래로 스크롤하면 다음과 같이 색상이 잘못 나타나는 현상이 발생하였습니다.
![](https://hackmd.io/_uploads/rJ9QRicjn.png =200x)


✅ 해결방법
- 해당 문제는 셀을 재사용하기 전에 초기화를 해주지 않았기 때문에 발생한 문제였습니다. `UICollectionViewCell`의 `prepareForReuse`메서드를 오버라이딩하여 Label의 text와 textColor를 초기화 해줌으로써 문제를 해결하였습니다.
```swift
override func prepareForReuse() {
    super.prepareForReuse()

    rankLabel.text = nil
    titleLabel.text = nil
    visitorLabel.text = nil
    rankChangeValueLabel.text = nil
    rankChangeValueLabel.textColor = nil
}
```

## 🙏 조언을 얻고 싶은 부분
### 1. collectionView() 메서드의 초기화 시점
- 처음 화면을 로딩할 때 비동기로 데이터를 받아오고, 아래의 `collectionView` 메서드에서 각각의 셀을 만들어 주고 있습니다.
```swift
extension DailyBoxOfficeViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 
        ...
    guard let data = boxOfficeData else {
            return cell
        }
        ...
    }
}
```
현재 `guard-let`구문에서 만약 들어온 데이터가 없다면 `cell`을 단순히 반환하기 때문에 아무것도 보이지 않고 셀을 나누기 위한 줄만 먼저 표시되고 있습니다. 이는 처음의 로딩화면을 구현할 때 `UIActivityIndicatorView`프로퍼티를 만들고`activityIndicator.layer.zPosition = 1`로 설정해서 뷰의 맨 위에 표시되도록 했습니다.
테스트를 하지는 않았지만, 
1. 현재 셀의 사이를 표시해주는 방식을 바꾸거나 
2. 데이터를 받아오기 전 까지 로딩을 위한 뷰를 따로 만들어서 표시해주는 방법도 있다고 생각합니다.
한 가지 더 생각한 방법이 
3. `collectionView`가 호출되는 시점을 늦추는 방법도 있다고 생각했었습니다. 
하지만 `UICollectionViewDataSource`프로토콜 때문에 자동적으로 호출되는 것 같습니다. 제가 찾아본 결과로는 막는것은 안된다는 것 같은데, 이를 피해 처음에 업데이트가 안되도록 막을 수 있는 방법이 있는지 궁금하고, 위의 두 방법 중 어떤 방법이 괜찮은지도 여쭤보고 싶습니다.


# PR STEP4
박스오피스 [Step4] Mary, idinaloq

@yim2627

## 🔍 고민되었던 점
### 1. LoadingView
- 데이터를 다운로드할 때 화면에 indicator만 표시해 주었던 이전 방법과는 다르게 화면 전체를 덮는 LoadingView를 새로 구현해주었습니다.
    ``` swift
    final class LoadingView: UIView
    ```
- 해당 방법으로 이전 스텝에서 발생하였던 문제(다운로드 완료 전까지 셀의 구분선이 화면에 먼저 표시되는 현상)도 해결하였으며, 새로운 ViewController에서도 활용할 수 있었습니다.

### 2. ScrollView ambiguous
- 처음에 scrollView의 autoLayout을 다음과 같이 설정하였습니다. 
    - stackView의 top은 imageView의 bottom으로 잡아주었습니다.
    - stackView의 bottom은 scrollView의 크기에따라 달라질 것이기 때문에 contentLayoutGuide에 잡아주었습니다.
    - scrollView의 가로 스크롤을 막기 위해 화면의 가로 사이즈인 FrameLayoutGuide에 leading과 trailing을 잡아주었습니다.
``` swift
NSLayoutConstraint.activate([
    ...
    contentStackView.topAnchor.constraint(equalTo: imageView.bottomAnchor),
    contentStackView.leadingAnchor.constraint(equalTo: frameLayoutGuide.leadingAnchor),
    contentStackView.trailingAnchor.constraint(equalTo: frameLayoutGuide.trailingAnchor),
    contentStackView.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor)
])
```
- 하지만 View Hierarchy에 다음과 같은 경고가 발생하였습니다.
`Scrollable content size is ambiguous for MovieInformationScrollView.`

- 아래 코드와 같이 widthAnchor를 scrollView의 frameLayoutGuide에 잡아주었더니 경고는 해결되었습니다.
이 부분에 대한 공부가 더 필요할 것 같습니다. 😭📚
``` swift
NSLayoutConstraint.activate([
    ...
    contentStackView.topAnchor.constraint(equalTo: imageView.bottomAnchor),
    contentStackView.leadingAnchor.constraint(equalTo: contentLayoutGuide.leadingAnchor),
    contentStackView.trailingAnchor.constraint(equalTo: contentLayoutGuide.trailingAnchor),
    contentStackView.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
    contentStackView.widthAnchor.constraint(equalTo: frameLayoutGuide.widthAnchor)
])
```

## 🙏 조언을 얻고 싶은 부분
### 1. 여러개의 비동기 작업 끝나는 시점
- `MovieInformationViewController`클래스에서 `receiveImageData()`메서드와 `receiveBoxOfficeData()` 메서드에서 비동기로 데이터를 처리하고 있습니다. 
뷰가 업데이트가 되는 시점(로딩뷰가 사라지는 시점)이 두 비동기 작업 처리가 끝날 때 이루어져야 하기 때문에 방법 중 하나로는 `count`를 지정해서 프로퍼티 옵저버로 로딩 뷰가 사라지도록 설정했습니다.
`dispatch Queue`의 group과 다른 방법들을 찾아보았는데 명확한 해답을 찾을 수 없었습니다.
여러개의 비동기 처리 작업이 끝난 것을 체크하는 방법이 따로 존재하는지 궁금합니다.

### 2. 프로젝트에서의 코드 재사용성
- 프로젝트 내용 구현에만 너무 힘을 쏟은 나머지 `MovieInformationViewController`클래스의 메서드 재사용성에 대한 문제가 있다고 생각합니다. 물론 수정을 하면 좋다고 생각합니다. 하지만 구현 하는것이 먼저라고 생각했고 수정하기에는 시간이 많이 필요할 것 같습니다. 때문에 이렇게 PR을 하게 되었습니다.

앞으로 남은 STEP들도 있을텐데, 이런식으로 하는 방법이 괜찮은 방법일까요...? 죄송합니다 😭 😭 😭 😭

### 3. 셀 재사용 시 구분선이 사라지는 현상
- 현재 첫 번째 박스오피스 리스트인 `DailyBoxOfficeViewController`에서 위아래로 스크롤하여 셀이 사라지게 하면 구분선이 간혹 사라지는 현상이 있습니다.
셀의 윗 부분에만 구분선을 그려지게 한 상태이고, 셀을 새로고침 하게 되면 다시 생기는데 셀의 재사용성과 관련이 있는걸까요...? 
셀이 재사용과 관련이 있다면 셀의 윗부분에만 계속 재사용되어야 하는게 아닌가 라는 생각이 들어 사라지지 않는게 맞다고 생각하는데 사라지고 또 새로고침을 하면 괜찮아 져서 어떤 이유때문에 이런 현상이 발생하는지 모르겠습니다.. 
- 참고로, 이전 스텝에서 피드백주신 대로 영화 제목 길이에 따라 cell의 높이를 유동적으로 변경해주었습니다. 이 부분도 해당 문제와 관련이 있을지 모르겠습니다 😭

<img src="https://hackmd.io/_uploads/SkOiV1-hh.png" width="300">

# PR STEP1
박스오피스II [Step1] Mary, idinaloq
@yim2627


## 🔍 고민되었던 점
### 1. iOS version 변경
`UICalendarView`를 iOS 16.0부터 지원하기때문에 deployment target을 14.0에서 16.0으로 변경하였습니다.

### 2. UICalendarView 적용하기
`UICalendarView`를 사용하기위해 공식문서와 검색을 했습니다. 비교적 최신 기능이라 그런지 공식문서 이외의 자료가 많이 부족했습니다.
결국 공식문서를 보면서 여러가지 테스트를 해서 이번 스텝에 적용할 수 있었고, 덕분에 apple 공식문서 보는 연습이 많이 되었다고 느껴졌습니다.

### 3. ViewController간 데이터 전달
`UICalendarViewController`에서 사용자가 선택한 날짜에 따라 BoxOffice를 업데이트하기위해 날짜를 전달해야했습니다.
저희는 `CalendarDelegate` 프로토콜을 활용하여 `UICalendarViewController`에서 날짜가 변경되었을 때 `DailyBoxOfficeViewController`의 `updateBoxOffice(date: Date)`메서드를 호출하도록 구현하였습니다.

## 🙏 조언을 얻고 싶은 부분

구조체를 열거형으로 바꿔주신 이유가 있을까요? 최대한 자세히 말씀해주시면 좋을 것 같습니다.

해당 부분을 열거형으로 바꿔준 이유는 아래와 같습니다.

- DateManager 객체를 만들어 준 이유
    - `DateManager`를 통해 DateFormatter 인스턴스의 재사용성을 높이고자 하였습니다.
- struct로 생성 시
    - `DailyBoxOfficeViewController`에서 어제 날짜 가져오기 및 date format을 변경하기 위해 `DateManager` 인스턴스를 생성해야 합니다.
    - `CalendarViewController`에서도 어제의 날짜를 가져오기 위해 `DateManager` 인스턴스를 생성해야 합니다.
    ➡️ 인스턴스가 총 2번 생성됩니다.
- enum으로 생성 시
    - static let은 thread safe하기 때문에 여러 ViewController에서 `DateManager`를 활용해도 인스턴스가 1번만 생성됩니다.

➡️ 따라서 인스턴스 생성 횟수 측면으로 보았을 때 enum을 사용하는 것이 더 나을 것 같다는 생각을 해서 struct대신 enum을 사용하도록 변경하였습니다.


이전에도 enum과 struct에 대해 피드백을 받았었는데, 어떤 것이 더 좋은지 아직 잘 모르겠습니다😭 

enum으로 생성했을 때에는 타입 프로퍼티 및 타입 메서드가 앱이 종료될 때까지 메모리에서 사라지지 않는다는 단점이 있어서 메모리 관점으로 보면 struct가 나은 것 같기도 합니다...

``` swift
extension UICollectionViewCell {
    static let identifier: String = String(describing: UICollectionViewCell)
}
```


박스오피스II [Step2] Mary, idinaloq
@yim2627

## 🔍 고민되었던 점
### 1. View Controller에서 셀 변경하기
- 리스트에서 아이콘으로 모드를 변경해야되는 요구 사항이 있었습니다. 그래서 다음과 같은 방법들을 생각해보았습니다.
1. 모드에 맞는 셀로 다시 그리기
2. 그리드타입, 리스트타입으로 뷰를 각각 만들기
- 우선 첫 번째 모드에 맞는 셀로 다시 그리기 위해 `collectionView.register`로 등록을 각각 했으나, 등록이 되지 않는 문제가 있었습니다. 따라서 두 번째 방법을 선택하는 방향으로 진행을 했습니다. 하지만 두 번째 방법을 사용한다면 네비게이션컨트롤러 때문에 새로운 뷰를 띄웠을 때 뒤로가기가 생기기 때문에 이 방법 역시 할 수 없었습니다.
- 다시 처음으로 돌아가서 셀을 등록할 때 `forCellWithReuseIdentifier`에 기존에 만들어놓은 기능이 제대로 동작하지 않는 것을 발견하고 원인을 찾아본 결과 저희가 만든 뷰는 각각 `UICollectionViewListCell`, `UICollectionViewCell` 을 상속하고 있었는데, `UICollectionViewListCell`이 `UICollectionViewCell`을 상속하고 있었기 때문에 발생한 문제였습니다.
- 각각의 뷰에 `identifier`를 string으로 설정해서 수동으로 `identifier`를 입력하도록 변경하였습니다. 


## 🙏 조언을 얻고 싶은 부분
### 1. collectionView 메서드
- 화면 모드가 추가되면서 모드에 따라 collectionView의 Cell과 그 Cell의 size들이 달라져야 했습니다. 따라서 collectionView의 `cellForItemAt`, `sizeForItemAt` 메서드가 매우 거대해졌습니다.
- 메서드를 여러개로 분리하는 방법을 생각해보았으나, 가독성을 좋아지게 하는 괜찮은 방법을 찾지 못했습니다.
- 따라서 `isListMode`로 분기처리하는 로직을 그대로 두었는데, 이 부분에 대해 수정이 필요해보이시는지 궁금합니다!

### 2. 모드에 따라 다른 constraint 적용하는법
- 기존 모드(리스트)에서는 cell의 구분선을 화면의 width만큼 그려주게 하기 위하여 collectionView의 layout의 leading과 trailing을 safeArea와 맞춰주었습니다.
- 하지만 이 상태로 아이콘 모드를 실행하면 cell이 화면 좌우에 여백없이 붙어서 요구사항과는 조금 다른 형태로 그려졌습니다.
![](https://hackmd.io/_uploads/HkwZTH523.png =200x)
- 따라서 collectionView의 constraint를 모드에 따라 다르게 적용할 수 있는 방법을 생각해보다가 NSLayoutConstraint의 [`deactivate(_:)`](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526066-deactivate)메서드를 발견하였습니다. 해당 메서드는 `activate`와는 반대로 NSLayoutConstraint 배열이 들어오면 각각을 비활성화 하는 메서드입니다.
- 위 메서드를 이용하여 아이콘 모드로 변경할 때 리스트 모드에서 잡아주었던 constraint를 deactivate하고, 새로운 constraint를 activate해주면 원하는대로 동작할 것 같았는데, 다음과 같이 constraint 충돌이 발생하였습니다.
```
[LayoutConstraints] Unable to simultaneously satisfy constraints.
"<NSLayoutConstraint:0x600002ac8820 UICollectionView:0x132012200.trailing == UILayoutGuide:0x6000030f0620'UIViewSafeAreaLayoutGuide'.trailing   (active)>",
"<NSLayoutConstraint:0x600002ac88c0 UICollectionView:0x132012200.trailing == UILayoutGuide:0x6000030f0620'UIViewSafeAreaLayoutGuide'.trailing - 10   (active)>"
```

``` swift
// 기존 제약 deactivate
NSLayoutConstraint.deactivate([
    collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
    collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])

// 변경 제약 activate
NSLayoutConstraint.activate([
    collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
    collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10)
])
```
- 혹시 해당 오류를 해결할 수 있는 방법이 있을까요...? 😥 또는 위와 같은 상황에서 모드에 따라서 constraint를 변경해주는 방법 외에 다른 방법이 있을지 궁금합니다!

### 3. 다이나믹 타입의 적용 기준

- 다이나믹 타입을 적용할 때 셀의 크기가 제한되어있는 경우에
1. 셀의 크기가 한정되어 있다면 어떤것을 우선적으로 크기를 조절해야하는지
2. 셀의 크기를 변경해서라도 보여줘야 해야되는지
3. 아니면 셀에 스크롤 뷰를 추가해야되는지

위 두 가지 이외에도 많은 기준들이 있을텐데 어느것을 기준으로 해야될지 모르겠습니다. 가장 중요한 기준이 따로 있을까요?






질문
--------------------
1. 모드에 따라 다른 constraint 적용하는법
2. 다이나믹 타입 적용할 때 크기가 제한되어있는경우 어느정도까지 늘리거나 잘리게 하는지-
-------------------

셀이 커지거나 내용이 누락되면 안된다