### 규칙 - 월, 목: 오후 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. 다이나믹 타입 적용할 때 크기가 제한되어있는경우 어느정도까지 늘리거나 잘리게 하는지- ------------------- 셀이 커지거나 내용이 누락되면 안된다