TIL

61일차 TIL _ 팀프로젝트 회고

h_luz 2024. 5. 25. 00:15
거북이의 단어장 TURTLE VOCA

 

와이어 프레임

 

프로젝트를 하면서 어려웠던 점

  • 팀원분의 추천으로 View를 top, body, bottom으로 나눠서 진행했는데, ViewController에서만 할 수 있는 기능들을 추가하기 위해 연결하는 부분에서 어려움을 겪었다. (처음엔 저번 개인과제 때 사용해 봐서 익숙한 delegate 방식을 많이 사용했다. 그러나 여러 파일에 데이터를 전달해야 할 때 너무 헷갈리고, 복잡하다는 생각이 들었다. 그리고 이 방식 외에도 다른 간편한 방식이 있다는 것을 알게 되었음)

    1-1. Delegate 방식

//AddBookCaseBodyView.swift
protocol AddBookCaseBodyViewDelegate: AnyObject {
    func didSelectImage()
}

이미지 선택 시 PHPicker를 띄우기 -> PHPicker를 present하려면 ViewController에 해줘야 하기 때문에 protocol을 사용해서 연결해 준다.

 

//AddBookCaseViewController.swift

extension AddBookCaseViewController: AddBookCaseBodyViewDelegate {
    //이미지 선택시 PHPicker present
    func didSelectImage() {
        var configuration = PHPickerConfiguration()
        configuration.filter = .images
        configuration.selectionLimit = 1
        
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self
        present(picker, animated: true, completion: nil)
    }
}

 

이렇게 extention을 사용해 줘서 Delegate 할 함수들을 따로 분류할 수 있음. 이렇게 Delegate 패턴으로 연결해서 사용하는 방법이 있다.

 

    1-2. NotificationCenter 방식

여러 객체가 동일한 이벤트에 응답해야 할 때 매우 유용

func setupKeyboardEvent() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(keyboardWillShow),
                                           name: UIResponder.keyboardWillShowNotification,
                                           object: nil)
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(keyboardWillHide),
                                           name: UIResponder.keyboardWillHideNotification,
                                           object: nil)
}

@objc func keyboardWillShow(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
        let keyboardHeight = keyboardSize.height
        // 키보드가 나타날 때 화면을 이동시키는 코드
        // 예를 들어, 키보드에 따라서 frame을 조정하는 방식 (키보드가 올라올 때 뷰를 위로 이동)
    }
}
    
@objc func keyboardWillHide(notification: NSNotification) {
    // 키보드가 사라질 때 화면을 원래 상태로 되돌리는 코드
    // 예를 들어, 키보드에 따라서 frame을 원래 위치로 되돌리는 방식
}

이 함수의 경우 키보드가 올라올 때 텍스트 필드를 가리지 않도록 frame의 위치를 위로 이동시키는 기능을 구현할 때 사용했는데,

이렇게 NotificationCenter를 사용해 주면 한 번만 코드를 적어도 모든 뷰 컨트롤러에 동일한 이벤트 핸들러를 설정할 수 있고, 이벤트를 수신하고 적절하게 대응할 수 있음.

 

//AddBookCaseViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()
     
    view.backgroundColor = .white
        
    setupConstraints()
    bodyView.delegate = self
        
    setupKeyboardEvent()
}

이렇게 이 기능이 필요한 ViewController에 viewDidLoad에 호출해 주어서 간단하게 사용할 수 있다!

 

import Foundation

extension Notification.Name {
    static let sender = Notification.Name("sender")
    static let getData = Notification.Name("getData")
    static let count = Notification.Name("count")
}

* 아 그리고 팀원분의 꿀팁인데 Notification이름을 이런 식으로 static으로 저장해서 사용하면 좋다고 하셨다. 그럼 사용할 때는 네임 부분에 . 만 찍어도 이름이 자동으로 나와준다.

굳 👍🏻👍🏻

 

그리고 NotificationCenter와 Delegate 패턴의 차이점에 대해서 간단하게 이야기해보자면,

- NotificationCenter

: 이벤트를 여러 객체에게 브로드캐스트 하는 데 적합하며, 발신자와 수신자 간의 결합이 느슨하다.

  (but 디버깅이 어렵고, 성능 저하 가능성이 있음)

- Delegate 패턴

: 1:1 통신에 적합하며, 명확한 역할 분담과 직접적인 통신이 가능. ( but 발신자와 수신자 간의 결합도가 높아지고, 확장성 제한)

 

    1-3. UIResponder 사용

https://developer.apple.com/documentation/uikit/uiresponder

 

UIResponder | Apple Developer Documentation

An abstract interface for responding to and handling events.

developer.apple.com

iOS 이벤트 처리의 기본 클래스. 터치 이벤트, 모션 이벤트, 리모트 컨트롤 이벤트 등을 처리하는 객체가 이를 상속

 

import UIKit

extension UIResponder {
    var currentViewController: UIViewController? {
        return next as? UIViewController ?? next?.currentViewController
    }
}
//BookCaseHeaderView.swift
@objc func plusButtonTapped() {
    let addBookCaseVC = AddBookCaseViewController()
    addBookCaseVC.modalPresentationStyle = .fullScreen
    currentViewController?.present(addBookCaseVC, animated: true, completion: nil)
}
//AddBookCaseHeaderView.swift
@objc func backButtonTapped() {
    currentViewController?.dismiss(animated: true)
}

이런 식의 접근도 가능하고, 포커스 받고 있는 텍스트 필드를 찾을 때도 사용해 주었다.

//MARK: - 포커스 받고 있는 텍스트 필드 찾기
extension UIResponder {
    private static weak var currentResponder: UIResponder?
    
    public static func findFirstResponder() -> UIResponder? {
        UIResponder.currentResponder = nil
        UIApplication.shared.sendAction(#selector(UIResponder._trap), to: nil, from: nil, for: nil)
        return UIResponder.currentResponder
    }
    
    @objc private func _trap() {
        UIResponder.currentResponder = self
    }
}

 

세 패턴 비교

 

다들 장단점이 있기 때문에 상황에 맞춰서 알맞게 사용하는 것이 중요하다고 생각했다.

정리해 보자면 여러 뷰 컨트롤러에서 공통으로 처리해야 하는 경우 NotificationCenter를 사용하고, 특정 객체 간의 1:1 통신이 필요할 때는 Delegate를 사용, 그리고 현재 응답 중인 객체를 찾고자 할 때는 UIResponder를 사용하는 것이 적절하다.

 

* present를 ViewController에서만 해줄 수 있는 이유는 present(_:animated:completion:) 메서드가 UIViewController 클래스의 인스턴스 메서드이기 때문이다. UIViewController를 상속받은 클래스의 인스턴스에서만 호출할 수 있음

 

뷰 계층 구조 관리

: 뷰 컨트롤러는 화면에 표시되는 뷰의 계층 구조를 관리한다.

  새로운 뷰 컨트롤러를 표시하는 작업(ex. present 메서드)은 기존의 뷰 계층 구조에 새로운 뷰를 추가하는 것을 의미.

  이 작업은 뷰 컨트롤러에서 처리할 수 있다.

 

뷰 생명주기 관리

: 뷰 컨트롤러는 뷰의 생명주기(lifecycle)를 관리합니다.

  새로운 뷰 컨트롤러를 모달로 표시하면 표시되는 뷰 컨트롤러와 기존 뷰 컨트롤러 모두의 생명주기 메서드가 호출.

  이러한 생명주기 관리 또한 뷰 컨트롤러에서 처리할 수 있다.

 

전환 애니메이션

: present 메서드는 뷰 컨트롤러 간의 전환 애니메이션을 처리한다.

  이러한 애니메이션은 뷰 계층 구조 및 화면의 현재 상태와 밀접하게 관련되어 있기 때문에 뷰 컨트롤러에서만 관리할 수 있습니다.

 


2. 오토레이아웃 ( 항상 어렵고 내가 생각한 대로 나오지 않는 것 같다.. 기존 어플을 따라 하는 연습을 많이 해봐야 할 거 같음)

3. UIMenu 디자인 ( 구현하는 것은 생각보다 괜찮았지만, UIMenu를 디자인하고 싶었는데, 찾아보니까 UIMenu는 디자인할 수가 없다고 나와있었다. 그런데 다른 앱들을 보면 꾸며져 있는 것들이 있어서 UIMenu가 아니면 어떤 방식으로 구현되는지.. 그리고 커스텀하는 방식이 궁금했다.. )

5. 코어데이터 relationship ( + delete rule ) -> 이건 전에 올린 TIL에 잘 정리했음..

6. CollectionView에 애니메이션 넣기 ( Carousel Effect )

7. 생명주기 (이론으로 배웠을 때는 솔직히 잘 이해가 안 갔는데 조금 이해가 된 거 같다 )

ㄴ 전에 개인과제를 했을 때 튜터님이 피드백으로 뷰의 라이프 사이클을 잘 활용해서 viewWillAppear 시점에 reload를 시키는 것도 잘해주셨습니다! 한 가지 더 보태자면 지금의 방식이 당연히 틀린 건 아니지만 데이터의 변경이 없어도 화면에 진입할 때마다 불필요한 reload도 발생할 수 있겠다는 생각이 들었습니다 :) 어떻게 하면 이를 해결할지도 고민해 보세요~!

라고 말해주셨는데, 이번에 컬렉션뷰를 reload 하면서 NotificationCenter를 통해서 reload 해주면 불필요한 reload를 없앨 수 있다!

override func viewDidLoad() {
    super.viewDidLoad()
       
    NotificationCenter.default.addObserver(self, selector: #selector(didBookCase), name: NSNotification.Name("didBookCase"), object: nil)
 }
 
// 단어장 추가 시 relaod
@objc func didBookCase() {
    bodyView.configureUI()
}
extension AddBookCaseViewController: AddBookCaseBodyViewDelegate {
    // 저장 완료 시
    func addButtonTapped() {
        let alertController = AlertController().makeAlertWithCompletion(title: "저장 완료", message: "단어장이 저장되었습니다.") { _ in
            NotificationCenter.default.post(name: NSNotification.Name("didBookCase"), object: nil)
            self.dismiss(animated: true, completion: nil)
        }
        present(alertController, animated: true, completion: nil)
    }
    
    
extension EditBookCaseViewController: EditBookCaseBodyViewDelegate {
    // 수정 완료 시
    func editButtonTapped() {
        let alertController = AlertController().makeAlertWithCompletion(title: "수정 완료", message: "단어장이 수정되었습니다.") { _ in
            NotificationCenter.default.post(name: NSNotification.Name("didBookCase"), object: nil)
            self.dismiss(animated: true, completion: nil)
        }
        present(alertController, animated: true, completion: nil)
    }

이렇게 해줘서 저장, 수정 완료 시에 reload를 해줘서 불필요한 reload를 줄일 수 있음!

 

인상 깊었던 점

  • 원래 ImagePicker를 사용했었는데 PHPicker을 앞으로 쓰게 될 것이라는 정보를 얻었다!
  • UICollectionViewDiffableDataSource
  • Label Factory를 만들어서 다 같이 간편하게 사용했던 점
  • extension 사용해서 코드 정리(분리?)
  • Toolbar
  • @escaping으로 에러 처리
  • 스택뷰 사용
  • README 작성

 

아쉬웠던 점

- 로그인 기능 도전 해보고 싶었는데 아쉽다

- API 사용도 아직 미숙해서 더 공부해보고 싶었는데 아쉽다

- 더 추가하고 싶었던 기능이 있는데 아쉽다 ( 단어장 추가 시 중복내용 확인, 단어장 전체 삭제 기능, 단어장 고정 기능, 텍스트 필드 작성 시 count 되는 기능, Notification)

- MVVM 해보고 싶었는데 아쉽다

 

그래도 이렇게 끝나지 않고, 팀원들과 계속 개선해 나가기로 해서 좋았다!

앞으로 최종프로젝트가 남았는데 좋은 사람들과 함께 이번 프로젝트에서처럼 많이 배울 수 있었으면 좋겠다고 생각했다..!

화이티이이잉 !! 😆

 

https://github.com/SijongKim93/Vocabulary

 

GitHub - SijongKim93/Vocabulary

Contribute to SijongKim93/Vocabulary development by creating an account on GitHub.

github.com

 

 

p.s 개발자의 길은 멀고도 험한 것 같다... ⭐️