# 하모 하이~하이 안녕하세요, 엘림! 인호, 하모 입니다. 계산기 II Step1 PR 잘 부탁드립니다 ㅎㅎ ### 진행 단계 : Step1 ## 🌿 변경 사항 (코드 병합) #### Node 타입 - 제네릭 타입의 네이밍을`<Element>`로 표현한 `하모`의 코드를 사용했습니다. - `next` 프로퍼티에 값을 할당할 때 프로퍼티에 직접 접근하지 않고 `setNext()` 메서드를 통해서 접근하는 `하모`의 코드를 사용했습니다. ```swift final class Node<Element: CalculateItem> { private(set) var data: Element private(set) var next: Node? ... func setNextNode(destination: Node) { next = destination } } ``` #### 큐와 링크드 리스트 - `CalculatorItemQueue`를 `Node`로 바로 구현한 `인호`의 코드와 달리 `Queue`와 `LinkedList`가 분리된 `하모`의 코드가 확장성이 더 좋다고 판단하여 사용했습니다. - `CalculatorItemQueue`, `LinkedList`의 `isEmpty`가 프로퍼티로 사용된 `인호`의 코드를 사용했습니다. #### ExpressionParser - `input`을 전달받을 때, 공백을 포함하여 구현한 `인호`의 코드에서 사이드 이펙트의 우려가 있어 공백을 포함하지 않는 `하모`의 코드를 사용했습니다. #### (MVC)View 역할 분리 - `Label`과 `StackView`추가하는 기능을 `ViewController`파일(controller)에서 담당했던 `인호`의 코드와 달리 `View`폴더에 `LogStackView`파일을 분리하여 구현한 `하모`의 코드를 사용했습니다. ```swift final class LogStackView: UIStackView { private let operandLabel: UILabel = { ... } ... } ``` #### NumberFormater 분리 - CalculatorViewController에서 NumberFormater를 메서드 내부에서 정의하여 사용하는 부분을 String의 Extension에서 구현하여 사용하는 `인호`의 코드를 사용하였습니다. #### CalculatorViewController - 기존에 `isNegativeSign`이라는 `Bool` 타입의 전역프로퍼티를 조건으로 이용하는 `toggleSignButtonTapped()`` 로직보다 현재 입력된 `operand`의 앞부분을 체크하는 `인호`의 코드가 더 안전하다고 판단되어 `인호`의 코드를 사용하게 되었습니다. ```swift @IBAction func toggleSignButtonTapped(_ sender: UIButton) { ... if operand.starts(with: CalculatorConstant.negativeSign) { operand.removeFirst() } else { operand = CalculatorConstant.negativeSign + operand } ... } ``` - `calculate()` 메서드 내부에서 Formula 구조체의 result 메서드가 던진 에러를 try?로 핸들링하는 부분을 더 가독성 있게 표현하기 위해 do-catch로 에러를 핸들링하는 `인호`의 코드를 사용하게 되었습니다. - 피연산자 버튼의 `IBAction`이 여러개로 분리되어 있던`하모`의 코드에서 `IBAction`을 한개로 통일하여 중복성을 줄인 `인호`의 코드를 사용했습니다. - 코드 수정 전 ```swift @IBAction func operandButtonTapped(_ sender: UIButton) { ... } @IBAction func dotButtonTapped(_ sender: UIButton) { ... } @IBAction func zeroButtonTapped(_ sender: UIButton) { ... } @IBAction func doubleZeroButtonTapped(_ sender: UIButton) { ... } ``` - 코드 수정 후 ```swift @IBAction func operandButtonTapped(_ sender: UIButton) { ... switch sender.tag { case 0: guard !operand.isEmpty else { return } updateOperandLabel(with: sender.tag.description) case 1...9: updateOperandLabel(with: sender.tag.description) case 10: guard !operand.isEmpty else { return } updateOperandLabel(with: CalculatorConstant.doubleZero) ... default: return } ... } ``` - 입력 레이블을 업데이트 하는 메서드에서, 여러 조건문을 통해 예외사항을 확인해서 코드가 방대해진 `인호`의 코드에서 몇가지 `Bool`타입 프로퍼티를 이용한 `하모`의 코드를 사용했습니다. ## 🌿 (step2에서) 리팩토링할 내용 - `ExpressionParser`의 `parse`메서드에서 불필요하게 반복되는 고차함수가 있어서 리팩토링 할 예정입니다. - `CalculatorViewController`의 프로퍼티, 메서드의 네이밍을 리팩토링 할 예정입니다. - `scrollView`를 스크롤 하는 기능에서 `layoutIfNeeded`가 반영되지 않아서 위치를 수정할 예정입니다. - `CalculatorViewController`의 `updateOperandLabel`메서드에서 조건문이 여러번 등장하는 부분을 리팩토링 할 예정입니다. - `stackView`를 추가할때 `spacing`값을 추가할 예정입니다. ## 🌿 조언을 얻고 싶은 부분 - 저희가 코드를 병합한 부분과 전체적인 코드에 대한 엘림의 의견이 궁금합니다!! # **Anatomy of a UICollectionView** ![](https://i.imgur.com/pCPtASa.png) 1. UICollectionView 1. UITableView와 유사하게 콘텐츠가 표시되는 기본 view이다. 테이블 뷰와 마찬가지로 컬렉션 뷰는 UIScrollView 하위 클래스이다. 2. UICollectionViewCell 1. UITableViewCell과 유사하다. 이 셀은 뷰의 콘텐츠를 구성하며 컬렉션 뷰의 하위 뷰이다. 프로그래밍 방식으로 또는 Interface Builder 내에서 셀을 만들 수 있다. 3. Supplementary Views 1. supplementary view에는 있어야 하지만 셀에는 없어야 하는 표시할 추가 정보가 있는 경우 사용한다.일반적으로 header 또는 footer에 사용한다. <aside> 💡 컬렉션 뷰에는 데코레이션 뷰도 있을 수 있다. 데코레이션 뷰를 사용하여 유용한 데이터를 포함하지 않지만 컬렉션 뷰의 모양을 향상시키는 추가 뷰를 추가한다. </aside> # **Using the UICollectionViewLayout** 위에서 설명한 시각적 구성 요소 외에도 컬렉션 뷰에는 콘텐츠의 크기, 위치, 기타 속성을 담당하는 레이아웃 객체가 있다. 레이아웃 객체는 UICollectionViewLayout의 하위 클래스이다. 런타임 중에 레이아웃을 교체할 수 있다. 컬렉션 뷰는 한 레이아웃에서 다른 레이아웃으로 전환하는 애니메이션을 자동으로 생성할 수도 있다. UICollectionViewLayout을 서브클래싱하여 고유한 사용자 지정 레이아웃을 만들 수 있지만 Apple은 UICollectionViewFlowLayout이라는 기본 흐름 기반 레이아웃을 제공한다. 요소는 그리드 뷰와 같이 크기에 따라 차례로 배치된다. 이 레이아웃 클래스를 즉시 사용하거나 하위 클래스로 분류하여 흥미로운 동작과 시각적 효과를 얻을 수 있다. --- <aside> 💡 스크롤 방향에 관한 프로퍼티는 UICollectionViewFlowLayout에 고유하고 기본값은 수직이다. vertical flow layout은 레이아웃 클래스가 뷰의 오른쪽 가장자리에 도달할 때까지 뷰의 위쪽부터 왼쪽에서 오른쪽으로 항목을 배치하는 것을 의미한다. 어느 시점에서 다음 줄로 이동함 뷰에 맞지 않는 요소가 너무 많은 경우 사용자는 세로로 스크롤하여 더 많이 볼 수 있다. 반대로 horizontal flow layout은 아래쪽 가장자리에 도달할 때까지 뷰의 왼쪽 가장자리를 가로질러 위쪽에서 아래쪽으로 항목을 배치한다. 가로로 스크롤함 </aside> # **Feeding the UICollectionView** 컬렉션 뷰를 사용할 때 데이터 소스와 위임도 설정해야 한다. 역할은 아래와 같음 - UICollectionViewDataSource는 콜렉션 뷰의 항목 수와 해당 뷰에 대한 정보를 리턴한다. - UICollectionViewDelegate는 사용자가 셀을 선택, 강조 표시 또는 제거할 때와 같은 이벤트가 발생할 때 또 다른 알림을 받는다. UICollectionViewFlowLayout에는 delegate 프로토콜인 UICollectionViewDelegateFlowLayout도 있다. 레이아웃 동작을 조정하여 셀 간격, 스크롤 방향 및 셀 크기와 같은 항목을 구성할 수 있다. --- # **UICollectionViewFlowLayoutDelegate** ```swift // MARK: - Collection View Flow Layout Delegate extension FlickrPhotosViewController: UICollectionViewDelegateFlowLayout { // 1 func collectionView( _ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath ) -> CGSize { // 2 let paddingSpace = sectionInsets.left * (itemsPerRow + 1) let availableWidth = view.frame.width - paddingSpace let widthPerItem = availableWidth / itemsPerRow return CGSize(width: widthPerItem, height: widthPerItem) } // 3 func collectionView( _ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int ) -> UIEdgeInsets { return sectionInsets } // 4 func collectionView( _ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int ) -> CGFloat { return sectionInsets.left } } ``` 1. collectionView(_:layout:sizeForItemAt:)는 주어진 셀의 크기를 레이아웃에 알려준다. 2. 패딩이 차지하는 총 공간을 계산한다. n + 1개의 균등한 크기의 공간이 있다. 여기서 n은 항목의 수이다. 왼쪽 섹션 inset에서 공간 크기를 가져올 수 있다. 뷰의 너비에서 이를 배고 행의 항목 수로 나누면 각 항목의 너비가 된다 그런 다음 크기를 정사각형으로 반환한다. 3. collectionView(_:layout:insetForSectionAt:) 셀 헤더, 푸터 사이의 간격을 반환한다. 4. 이 메서드는 레이아웃의 각 줄 사이의 간격을 제어한다. 이 간격은 왼쪽과 오른쪽의 패딩과 일치해야 한다. [UICollectionView Tutorial: Getting Started](https://www.kodeco.com/18895088-uicollectionview-tutorial-getting-started)