## 그라운드 룰 https://hackmd.io/@xWUgo0xIRvyj0d0NJlwNiw/SkNe4xBqo https://dvlpr-chan.tistory.com/36 싱글톤 1. 변수 설정할 시 타입 명시 다 해주기 2. 코드 한 줄에 100자 이상 넘어갈 시 [참고](https://google.github.io/swift/#line-wrapping)해서 컨벤션 해주기 3. 매직넘버/매직리터럴 지양하기 ### 활동 시간 1. 활동학습 있는날에는 점심먹고 2시부터 진행, 없는 날에는 9시 30분 ~ 18시 2. 필요 시) 수요일 23:00 이후에 간단하게 1-2시간 회고 Time 3. 공식문서 공부 23:00~24:00부터 1-2시간 ### 짝프로그래밍 역할 순서 순서| 감시자 | 네비게이터 | 드라이버 ---|---|---|--- 1|세홍 | 릴라 | 무리 2|무리 | 세홍 | 릴라 3|릴라 | 무리 | 세홍 ### Karma Style - Commit message subject type - feat : 기능 구현 - refactor : 네이밍 수정 - docs : 문서 추가, 수정 - chore : 중요하지 않은(생산적이지 않은) 내용 수정 ### Commit 단위 하나의 기능 별로 나눠서 커밋하기 ## Step 1 : 쥬스 메이커 타입 정의 - FruitStore.swift 파일에 과일의 재고를 관리하는 FruitStore 타입을 정의합니다. - FruitStore는 다음의 조건을 충족해야 합니다. - FruitStore가 관리하는 과일의 종류 : 딸기, 바나나, 파인애플, 키위, 망고 - 각 과일의 초기 재고 : 10개 - 각 과일의 수량 n개를 변경하는 기능이 있습니다. - 이 외에 더 필요한 기능, 특성, 타입이 있으면 자유롭게 추가해도 좋습니다. - JuiceMaker.swift 파일에 다음의 조건을 충족하는 JuiceMaker 타입을 정의합니다. - FruitStore의 과일을 사용해 과일쥬스를 제조합니다. - 딸기쥬스 : 딸기 16개 소모 - 바나나쥬스 : 바나나 2개 소모 - 키위쥬스 : 키위 3개 소모 - 파인애플 쥬스 : 파인애플 2개 소모 - 딸바쥬스 : 딸기 10개 + 바나나 1개 소모 - 망고 쥬스 : 망고 3개 소모 - 망고키위 쥬스 : 망고 2개 + 키위 1개 소모 - 과일의 재고가 부족하면 과일쥬스를 제조할 수 없습니다. - JuiceMaker는 FruitStore를 소유하고 있습니다. - 이 외에 더 필요한 기능, 특성, 타입이 있으면 자유롭게 추가해도 좋습니다. (선택사항) 필요한 경우 오류처리(throw, try-catch)를 구현합니다. 만들 타입 Fruits Juice nested 타입으로 Recipe (몇개씩 필요한지 딕셔너리 리턴?) Error isStocked 구매함수는 함수 이름만 만들어놓고 속은 우리가 해야할거 초반 재고리스트 딕셔너리 했음 쥬스 레시피 어떻게 할당해줄지? 쥬스 만들기 함수 makeJuice 재고 차감을 어떻게 할건지 useFruit 재고 확인을 어떻게 할건지 isStocked [했음] 오류 처리는 어떻게 할건지 [오류 던지기는 했음] : FruitStore에서 오류를 던지고 JuiceMaker에서 오류를 받는식 (Result) --- # PR # Juice Maker🧃 [STEP1] 무리, 릴라, 세홍 @llingo 안녕하세요 링고! 무릴라홍입니다! 새해 첫 STEP1 PR보냅니다. 앞으로 3주간 잘부탁드립니다 :blush:~ ## 고민했던 점 🤯 ### 1️⃣ Fruits의 Initialize ```swift= // 초반 작성 코드 class FruitStore { private var strawberry: Int = 10 private var banana: Int = 10 private var pineapple: Int = 10 private var kiwi: Int = 10 private var mango: Int = 10 } ``` - 처음 작성한 방향에서는 과일들을 각각의 프로퍼티로 `FruitStore` 에 생성하였으나 `JuiceMaker` 와 데이터를 주고 받는 과정에서 계속해서 switch문으로 어떤 과일인지 식별해줘야 하는 큰 번거로움이 있었습니다. - 이를 개선하기 위해 `Dictionary` 사용을 결정했습니다. 저희 조는 `Magic Number` 사용을 지양하기 위해 처음 Dictionary 각각의 Key(String)값의 Value(Int) 를 init()에 Value를 프로퍼티로 할당을 하려했으나 프로퍼티들이 `initialize` 되기전에는 할당을 할 수 없다는 에러가 발생해 다른 방법을 고민했습니다. ```swift= private(set) var fruits: [Fruit : Int] = [:] private init() { initializeDefaultStock() } private func initializeDefaultStock() { let defaultStock = 10 for fruit in Fruit.allCases { fruits[fruit] = defaultStock } } ``` - 기존 `FruitStore`에 존재하던 프로퍼티들은 `Enum` 타입으로 새로 정의하고 `FruitStore` 내에서는 `Dictionary[Fruit : Int]`만을 프로퍼티로 갖게 수정했습니다. 또한 `initialize` 될 때 각각의 과일 일때의 `Value` 값에 접근하여 `default` 값인 10을 할당 해주었습니다. --- ```swift= switch juice { case .strawberryJuice: fruitStore.useFruits(fruits: .strawberry, count: Recipe.usingStrawberry) case .bananaJuice: fruitStore.useFruits(fruits: .banana, count: Recipe.usingBanana) // ... case .mangoKiwiJuice: fruitStore.useFruits(fruits: .mango, count: Recipe.usingMixMango) fruitStore.useFruits(fruits: .kiwi, count: Recipe.usingMixKiwi) } ``` ### 2️⃣ 각 주스별 recipe ```swift= // 초반 recipe 구현부분 enum Recipe { static let usingStrawberry = 16 static let usingBanana = 2 // ... static let usingMixMango = 2 static let usingMixKiwi = 1 } ``` ```swift= // Nested 타입을 사용한 Juice타입 내 구현 부분 var recipe: [Fruit : Int] { switch self { case .strawberryJuice: return [.strawberry : 16] case .bananaJuice: return [.banana : 2] // ... case .strawberryBananaJuice: return [.strawberry : 10, .banana : 1] case .mangoKiwiJuice: return [.mango : 2, .kiwi : 1] } } ``` - 초반 **Nested Type**에 대해 알지 못하여 `Recipe`타입을 따로 정의하여 진행했습니다. 해당 과일쥬스에 `Int`값으로 소모되는 과일의 갯수는 할당해주었지만 **어떠한 과일인지** `Fruit`타입은 사용할 수 없었습니다. - **Nested Type**과 딕셔너리를 사용하여 `recipe`마다 `Fruit`타입의 과일이 몇개 소모되는지를 구현할 수 있었습니다. ### 3️⃣ Singleton - `class FruitStore` 같은 경우 처음 인스턴스화 되고 나서 그 인스턴스가 다른 인스턴스로 변경되는일이 있어서는 안된다고 생각했습니다. 재고를 관리하는 역할이었기 때문에 실수로 라도 새로운 인스턴스가 생성되어 값이 수정되어서는 안되기 때문에 단 하나의 인스턴스만을 보장하는 `Singleton` 패턴을 사용하게 되었습니다. ### 4️⃣ 오류처리 - 캠핑사이트에서 필요시 오류처리에 대한 부분을 throw, try-catch로 구현해보라고 안내되고 있습니다. 하지만 팀원 중 과반수 이상이 `Result`로 오류처리하는 방식을 써보지 않았고, 가독성이 좋다고 생각하여 `Result`로 사용해보았습니다! - 오류를 던지는 부분과 받는 부분이 어느곳에 위치해야 하는가에 대한 고민을 해보았습니다. 팀원들과의 의견을 나눈 결과로 실제 과일의 재고를 체크하는 `FruitStore` 에서 오류를 던지고 Fruit 재고를 확인하는 함수를 사용하는 `JuiceMaker` 에서 오류를 받아내는 방향으로 코드를 작성 했습니다. ### 5️⃣ 은닉화 - 먼저 STEP1의 기능을 모두 구현한 후 접근 제어에 대해 고민을 해보게 되었습니다. - `class FruitStore`의 `Dictionary fruits`의 경우 `private(set)`을 사용하여 외부에서는 읽기만 가능하도록했습니다. - 또 `FruitStore`의 메서드 중 외부에서 접근할 필요가 없는 메서드는 `private`을 사용하여 은닉화하였습니다. ### 6️⃣ final class - `class FruitStore`의 경우 상속을 받거나 상속을 하지 않기때문에 `final` 키워드를 사용해주었습니다. --- ## 조언을 얻고 싶은 부분 🙇🏻‍♂️ ### 로직의 최적화 - 현재 저희 코드에서는 Dictionary로 어떤 과일인지와 얼마나 필요한지를 주고받고 있습니다. 이 과정에서 과일을 식별하는 과정이 필요했고 이 과정을 요구하는 모든 로직은 for 문과 옵셔널 바인딩을 통해 진행하였습니다. 혹시 더 간결한 로직이 있을지 다른 방향으로 생각할 수도 있는지 조언이 필요합니다. --- # 링고 코멘트 ## P1 1. FruitStore.swift의 `func isStocked(recipe: [Fruit : Int]) -> Result<Bool, JuiceMakeError>` - 매개변수에 recipe가 저장되어있나요?라는 뜻으로 읽힘 - Argument Label을 사용 or 네이밍 변경해보자 2. FruitStore.swift의 `func useFruit(recipe: [Fruit : Int])` - 위 코멘트와 동일 3. Juice.swift의 `var recipe: [Fruit : Int]` - [키 : 값]이 아닌 [키: 값]의 형태로 4. JuiceMaker.swift 19~23줄 - 실패보다는 성공했을때가 위에 오는것이 좋다 - Result의 결과로 result 이름이 중복되는것보다는 다른 네이밍으로 - case 사이엔 한칸 띄워주는것이 가독성에 좋다 5. JuiceMaker.swift `func completeOrder(menu: String)` - 함수의 네이밍과 동작이 다름 //현재는 출력하는것같다 ## P2 1. FruitStore.swift ```swift // private(set) var fruits: [Fruit : Int] = [:] private(set) var fruits: [Fruit: Int] = [:] ``` 2. Juice.swift의 `var menu`프로퍼티 - 현재 메뉴프로퍼티는 쥬스의 이름을 전달하는 역할 - 네이밍은 외부에서 사용했을때 이름만으로 해당 기능의 결과를 예상할 수 있어야 함 3. JuiceMaker.swift의 `func makeJuice(juice: Juice)` - 네이밍이 makeJuice(juice:)로 읽힘 - Argument Label을, 가이드라인 참고해서 네이밍 변경해보자 4. JuiceMaker.swift의 `let fruitStore` - 외부에서 열람할 수 있는것같다 ## P3 1. LocalizedError 찾아보기 2. FruitStore는 클래스, JuiceMaker는 구조체로 작성해주셨는 데 이렇게 구현하시게된 이유가 있을까요? --- 컴온컴온 [Properties 공식문서](https://docs.swift.org/swift-book/LanguageGuide/Properties.html) 중 `Stored Properties of Constant Structure Instances` fruitStore는 Class `Reference Type`입니다. 해당 클래스 내에서는 인스턴스가 변경되는 일이 있어선 안되기 때문에 let 으로 선언을 했습니다. let으로 선언이 되었기에 만약 struct 로 구현을 하게 된다면 과일재고 collection 인 fruits 의 내부 값 변경을 할 수가 없습니다. 그렇기 때문에 Class 로 구현을 했습니다. Struct는 `Value Type` 이기 때문에 프로퍼티나 인스턴스 둘 중에 하나라도 let 으로 설정을 하게되면 프로퍼티의 값을 변경할 수 없습니다. 따라서 내부의 프로퍼티의 값을 변경할 일이 없는 JuiceMaker를 struct로 구현했습니다. --- --- # 쥬스메이커🧃 [STEP 2] 무리, 릴라, 세홍 @llingo 링고 무릴홍 스텝2 PR 올립니다 🙇🏻‍♀️🙇🏻‍♂️🙇🏻‍♀️ 이번에는 활동학습때 새로배운걸 적용해보는 쪽에 시간을 많이 써봤습니다! ## 고민했던 점❓ ### 1️⃣ 화면전환의 오류 - 쥬스 만드는 화면에서 재고수정 버튼을 누르면 아래와 같은 오류가 떴습니다. - >Thread 1: "Storyboard (<UIStoryboard: 0x60000232ea00>) doesn't contain a view controller with identifier - 스토리보드에서 연결을 시켜주었다고 생각했는데 오류를 보니 뷰컨트롤러의 identifier가 설정되어있지 않아서 발생하는 오류였습니다. 스토리보드에서 `Storyboard ID`를 설정해주니 오류가 사라지고 정상적으로 작동하는 모습을 볼 수 있었습니다. ### 2️⃣ 버튼 액션함수의 재활용 - 쥬스를 만드는 버튼이 쥬스의 종류만큼 있었기에 기존대로 하려면 7개 각각에 해당하는 액션을 다 작성해야 했습니다. 하지만 코드를 작성하던 중 쥬스를 만드는 기능은 다 똑같고 쥬스의 종류만 달라지는건데 액션하나를 공유해서 같이 쓰는 방법은 없을까 고민을 해봤습니다. <img width="400" alt="image" src=https://i.imgur.com/yCm4ttE.png> </br> </br> - 각 버튼의 `Restoration ID` 를 해당 쥬스의 이름으로 설정해 액션함수의 매개변수인 sender로 접근해 해당 이벤트를 보낸 인터페이스 객체가 무엇인지 식별한 뒤 각각의 쥬스에 맞는 함수를 불러와 작동했습니다. ```swift @IBAction func makeJuiceTapped(_ sender: UIButton) { let juiceMaker = JuiceMaker() guard let senderID = sender.restorationIdentifier else { return } switch senderID { case "OrderStrawberryBanana": juiceMaker.makeJuice(.strawberryBananaJuice) // .... case "OrderMangoKiwi": juiceMaker.makeJuice(.mangoKiwiJuice) default: return } } ``` ## 조언을 얻고싶은 부분 ### 1️⃣ 코드의 정리 - 함수의 갯수가 점점 많아지면서 코드의 가독성이 떨어지는 걸 느꼈습니다. 기능에 따라 함수의 정의 위치를 조정을 해보고자 했습니다. 서로 영향을 미쳐 연관이 있는 함수끼리 일단 붙여봤는데, 적절하게 코드의 위치를 관리하는 방법의 조언이 필요합니다! ### 2️⃣ NotificationCenter Observer를 등록하는 위치 - 팀프로젝트 시작 전에 구현해야할 기능과 네이밍에 대한 고민을 잠깐 하는 시간이 있었는데 Observer를 두는 위치에 대해 팀원들의 의견이 달랐습니다. Model에 가까이 두고싶다는 의견과 View Controller에 두는게 맞지 않을까하는 의견이 둘 다 나왔습니다. - Model에 두고싶다는 의견은 Model은 데이터의 관련된 작업만 해야한다고 생각했고, View의 영역( ex. 인터페이스를 건드리는)은 건드리면 안된다고 생각했습니다. - View Controller에 두고싶다는 의견은 "`Observer`에 의해 바뀌는 값이 표시되는건 View에서 표시되기때문에 View Controller에 두어도 상관이 없을것같다" 였습니다. - Observer의 위치에 대해서 링고는 어떻게 생각하시나요? ### 3️⃣ Restoration vs Accessibility <img width="300" alt="image" src=https://i.imgur.com/f6kP15A.png> - sender의 프로퍼티에서 식별자를 접근하던 중 두가지 항목이 있었던걸 발견했습니다 `Restoration ID` 와 `Accessibility Identifier` 가 있었는데, 이 두가지의 사용하는 곳이 다른걸 까요? 어떨때 어떤 것을 사용해야하는 건지 조언이 필요합니다! --- # 이번주README ## 트러블슈팅 ### 화면전환 - Thread 1: "Storyboard (<UIStoryboard: 0x60000232ea00>) doesn't contain a view controller with identifier - [오류해결 참고 링크](https://velog.io/@junsuboy/Swift-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-doesnt-contain-a-view-with-identifier-) [ 키 : [키: 값]] --- [STEP 2] 화면전환 : present 쥬스만드는 View - label 재고 할당하기 - 쥬스제조 버튼이랑 makeJuice 함수 연결하기 - 쥬스제조시 Alert 띄우기 - 성공시 ok 버튼 하나만 단다 - NotificationCenter가 재고 바꿔주기 - FruitStore 안에서 fruits를 감시하고있기 - 실패시 ok 버튼이랑 재고수정(모달띄우기) - 재고수정 클릭시 재고수정 Modal 띄우기 NotificationCenter 2개 - 재고사용하는 식별자 : useFruitEvent - 재고추가하는 식별자 : updateFruitStockEvent 재고수정 View(Modal) - 추가하기 버튼 만들기 - 추가하기 버튼 눌렀을때 Alert 띄우기 - 성공 - 성공 안내문 출력 OK 띄우기 - NotificationCenter에 push 하기 - 실패(기존 수량보다 더 -하게되면) - 재고 부족 띄우기 OK 띄우기 - 모달종료 버튼 (왼쪽 위) 옵저버를 어디에 둬야하나,,, -> 일단.. controller에 둬보자 ViewController 데이터를 수정하게되면 여기안에서 Model을 건드리는거다 Model에서 이벤트를 감시하고있다가 여기서 데이터수정이 일어났음 좋겠다. @IBOutlet weak var --- STEP3 23.01.10 오늘의 학습 목표 1. 오토레이아웃 학습하기 2. 뷰 Push 로 전환하기 학습하기 - 화면 전환할 때 데이터 전달하기 까지 3. 스텝퍼 학습하기 - (재고수정)화면 제목 ‘재고 추가’ 및 ‘닫기’ 버튼 구현 - 닫기를 터치하면 이전화면으로 복귀 - 모달->네비게이션으로 바꾸기 - (재고수정)화면 진입시 과일의 현재 재고 수량 표시 - 화면전환시 데이터 전달(fruitstore.fruits) - -, + 를 통한 재고 수정 - 버튼 클릭으로 재고 실시간 변경 - iPhone 12 외에 다른 시뮬레이터에서도 UI가 정상적으로 보일 수 있도록 오토레이아웃 적용 mapValue메소드가 하는 역할 > Returns a new dictionary containing the keys of this dictionary with the values transformed by the given closure. - 기존의 딕셔너리의 키값에 주어진 클로져에 의해 변환된 값을 주고 새로운 딕셔너리로 반환해주는 메서드입니다. - 코드에서는 fruits 딕셔너리가 [Fruit: Int]에서 [Fruit: String]으로 변환하게됩니다. 재고 레이블에 넣기 위해 Int값을 String으로 변환하게되었습니다. [태그값설정하기](https://stackoverflow.com/questions/30046540/get-button-pressed-id-on-swift-via-sender) 학습활동시간에 배운것을 사용해보고자 선택한것이 가장 큰 이유였습니다. 다른 화면 간 데이터 전달 방법에는 1. 프로퍼티로 직접 접근해서 데이터 넘기는 방식 2. segue를 이용해서 전달하는 방식 3. delegate 패턴을 이용한 방식 4. closure를 이용한 방식 5. notification center - `notification center`는 broadcast에서 많이 사용한다고 하여 다른 방법을 사용해보기로 했습니다. `KVO` 적용 시 이미 `ViewController`가 `UIViewController`상속을 받고있기 때문에 적용이 어려워 다른 방법을 찾아보았습니다. - `delegate`를 사용하려고 적용했을 시, `delegate`의 인스턴스가 생성되는 시점이 `ViewController`의 `viewDidLoad()`보다 앞선 시점이라 해결이 어려웠습니다. 또 질문을 통해 이렇게되면 Singleton, (성공/실패 알람을 위한)notification에 delegate까지 사용하게되어 비효율적이라는 말을들었습니다. - 따라서 선언해놓은 Singleton을 최대한 이용하는 방향으로 진행하게 되었습니다. --- # STEP3 구현해야할 것 : 다음주 화요일까지(17일) - 화면 제목 ‘재고 추가’ 및 ‘닫기’(우측 상단) 버튼 구현 -> 네비게이터? - 닫기를 터치하면 이전화면으로 복귀 - 화면 진입시 과일의 현재 재고 수량 표시 -> 싱글톤..? - -, + 를 통한 재고 수정 -> stepper - iPhone 12 외에 다른 시뮬레이터에서도 UI가 정상적으로 보일 수 있도록 오토레이아웃 적용 1. 오토레이아웃 2. 네비게이터 건드려보기 3. 바꾼 값을 닫기 눌렀을 때 재고 변동을 시켜야 함...! 델리게이트 닫기 눌렀을 때 frutis private set 6 모여어어엇 --- STEP 3 PR # Juice Maker🧃 [STEP3] 무리, 릴라, 세홍 @llingo 안녕하세요 링고! 무릴라홍입니다! 새해 첫 STEP3 PR보냅니다. 이번엔 익숙하지 않은 내용들이 많아 시간이 좀 오래 걸렸네요 😂 ## 고민했던 점 🤯 ### 1️⃣ 과일 종류에 맞는 Stepper 와 Label 의 연결 - 저희가 UI 상으로 생성한 Stepper 와 Label 을 각각에 맞는 과일과 연결하는 부분에서 많은 고민을 했습니다. 작성자의 입장에선 네이밍을 통해 구분이야 할 수 있겠지만은 컴파일러는 모른다는게 문제였습니다. - 그래서 과일을 넣게 되면 해당 과일에 맞는 Stepper 또는 Label 를 반환하는 메서드를 만들어 과일과 UI 의 연관성을 생성 해주었습니다. ```swift func returnStepper(of fruit: Fruit) -> UIStepper? { let stepperDictionary: [Fruit: UIStepper] = [.strawberry: strawberryStepper, .banana: bananaStepper, .pineapple: pineappleStepper, .mango: mangoStepper, .kiwi: kiwiStepper] guard let stepper = stepperDictionary[fruit] else { return nil } return stepper } ``` 위 코드는 예시로 [Fruit: UIStepper] `딕셔너리` 를 생성해 과일을 `Parameter` 로 받아 키값으로 넘겨준 뒤 나온 `Value` 를 리턴해주었습니다. ### 2️⃣ Stepper의 Value 설정 - Stepper의 Value 는 타입은 Double 이었기에 Double 타입으로의 형변환이 필요했습니다. 그래서 처음 생각한 것이 고차함수를 이용한 Value 들의 형변환 이었습니다. ```swift guard let fruitStock = fruitStore.fruits.mapValues{ Double($0) } else { return } ``` - 하지만 오류`(조언받고 싶은 부분 1)`들로 인해 다른 접근방식을 생각했고, 그렇게 생각한 것이 FruitStore 클래스에서 과일을 넣으면 재고갯수를 반환해주는 함수`(조언받고 싶은 부분 2)`를 생성했습니다 ```swift func returnFruitStock(of fruit: Fruit) -> Int { guard let stock = fruits[fruit] else { return -1 } return stock } ``` - 결론적으로는, EditStockViewController 에서 Fruit 타입전체를 도는 반복문으로 각각 과일에맞는 Stepper 에 해당 과일의 갯수를 Double 형으로 바꿔 Setting 해주었습니다. ### 3️⃣ 데이터 전달 방식에 대해 << 추가할 내용 - 앞선 스텝에서 고민했던 것 처럼 `EditStockViewController`에서 수정한 과일의 값을 어떻게 `ViewController`에 전달할 수 있을까 고민을 해보았습니다. - 데이터를 직접 처리하는 주체가 `FruitStore`였고 `FruitStore`가 `EditStockViewController` 객체를 가지고 있는게 어색했고 또 delegation의 인스턴스 할당 시점이 명확하지 않아 사용하지 않고 Sigleton으로 구현해보았습니다. ### 4️⃣ 오토 레이아웃 설정 - 앞서 로직을 다 구현해보고 오토레이아웃에 대해 고민을 해보았습니다. 공부하고 이해한 바로는 뷰의 양 옆과 위 혹은 아래에 제약을 걸어주면 오토레이아웃이 설정되는것으로 이해를 했습니다. - 중간에 위치시키기 위한 `Y축 정렬`과 뷰의 `leading`, `trailing`에 각각 제약을 걸고 실행해보았습니다. 결과적으로 X축에 대한 값이 없다며 오류가 발생했습니다. X축에 대한 값은 `leading`, `trailing`에 걸어 둔 제약으로 커버가 가능하다고 생각했는데 정확한 값이 아니었기때문에 발생했던 오류같았습니다. - `leading`, `trailing` 값과 함께 `top`을 Safe Area에 제약을 걸어주면서 정상적으로 작동하는 모습을 볼수있었습니다. 하지만 이렇게 되면 상대적으로 큰 화면인 기기에서는 너무 위쪽에 위치해있을수도 있다는 생각이 들었고 X축, Y축 정렬을 함께 써 정 가운데 정렬이 되는 방향으로 사용했습니다. ### 5️⃣ 네비게이션 바가 사라지지 않아 해본 고민 - STEP3 요구사항 중 네비게이션 바에 타이틀 `재고 추가`와 `닫기` 버튼을 구현해야했습니다. 이미 구현 되어있는 네비게이션 바가 존재했고, 이 네비게이션 바가 사라지지않아 고민을 하게 된것같습니다. - 사라지지 않았던 이유는 modal 방식이 아니라 push로 넘겨주고있었기 때문이었습니다. modal방식으로 넘어갈 수 있게 수정한 뒤, `Navigation Controller`에서 `Shows Navigation Bar`옵션을 해제해주고 `EditStockViewController`에서 새로운 네비게이션 바와 버튼을 추가하는 방식으로 진행했습니다. ## 조언 받고싶은 부분🙇🏻‍♂️ ### 1️⃣ 고차함수 사용할때의 오류 `Anonymous closure argument not contained in a closure` - 전에도 몇번 접했던 오류였는데, 구글링을 통해도 이해가 잘 가질 않습니다 링고... ㅜㅜ 조금 막연한 질문일 수 도 있지만은 이 오류에 대해서 간략하게 알려주실 수 있을까요! ### 2️⃣ Return 시 불필요한 코드의 처리 - 과일의 재고값을 반환하는 로직중 옵셔널바인딩을 처리하는 과정에서 어쩔 수 없이 `무의미한 return` 을 해야해서 임의로 -1 를 반환하도록 하였습니다. - 현재 자신이 가진 Dictionary 만 돌기에 -1 이 return 되는 경우는 없다고 확신할 수 있지만 `MagicNumber` 의 위험이 있을 수 있다 생각합니다 - 이런경우 어떻게 처리를 해야할까요??