Skip to content

Kabinett 최종보고서

ksiomng edited this page Sep 9, 2024 · 65 revisions

1. 기획 의도

그리운 기억을 한 곳에 모아두기 위한 따뜻한 의도에서 출발했습니다. 온라인과 오프라인, 디지털과 아날로그의 경계를 넘어, 우리는 사람들이 다양한 플랫폼에 흩어져 있는 편지들을 모아 보관하고, 그 속에 담긴 소중한 감정과 추억을 오랫동안 기억할 수 있도록 돕고자 합니다.

Kabinett 앱은 주변 사람들과 함께 글이나 사진 등 소중한 추억을 기록하고 보관할 수 있는 공간을 제공하고자 기획되었습니다. 이 앱을 통해 사용자들은 함께한 순간들을 특별한 형태로 간직하며, 더욱 의미 있는 추억을 만들어갈 수 있습니다.

이제는 기념일에도 직접 편지를 쓰기보다는 여러 소셜 플랫폼을 통해 마음을 전달해요. 여러 플랫폼에 흩어져 있는 편지들은 쌓이는 대화와 정보들 속에 쉽게 묻혀버리곤 해요. Kabinett은 기억하고 싶은 소중한 마음과 편지를 한 곳에 모아 간직할 수 있도록 도와줘요. 긴 마음을 전하고 싶을 때는 원하는 봉투와 우표로 꾸며 전달할 수 있고, 그 편지를 특별한 장소에 두어 영원히 그곳에서 열어볼 수도 있어요. Kabinett은 소중한 마음과 글, 머문 장소까지 모으고 기억할 수 있는 공간이에요.

2. 프로젝트 기간

2024.08.05 ~ 2024.09.13 (ing)

3. 주요 사용 기술

  • SwiftUI
  • UIKit
  • Combine
  • Firebase
  • Kingfisher
  • PHPicker

4. 기능 설명

4.1 편지 보관함

<홈 화면>

  • 전체 편지 리스트를 열람
  • 미열람 편지 갯수 표시
  • 앱 최초 실행 시 토스트 메시지 표시

<편지 리스트 화면>

  • 편지 리스트 열람
  • 미열람 편지 표시
  • 검색 기능 제공 (키워드/날짜)

<편지 화면>

  • 편지 열람 (글/이미지)

4.2 편지 쓰기

편지 작성 방식은 **"편지 불러오기"**와 "편지 쓰기" 중에서 선택 가능

<편지 불러오기>

  • 카메라 또는 앨범에서 이미지 선택
  • 대상 선택 및 발송 날짜 입력
  • 편지봉투와 우표 선택 후 저장

<편지 쓰기>

  • 로그인 여부에 따른 유저 선택 기능
  • 편지지, 폰트 선택 기능
  • 편지 작성 시 사진 첨부 가능
  • 우표와 봉투 선택 가능, 실시간 미리보기 제공
  • 편지 프리뷰 화면에서 편지 보내기

4.3 프로필

사용자 상태에 따라 프로필 기능 제공. 로그인 후에만 모든 기능 사용 가능.

<로그인 기능>

  • Apple 로그인, Kabinett 번호 생성
  • 익명 유저 상태로 일부 기능 사용 가능

<프로필 설정>

  • 프로필 정보 변경 및 이미지 크롭
  • 로그아웃 및 회원 탈퇴 가능

5. 기술 설명

5.1 공통

  • 비즈니스 로직 분리: ViewModelUseCase로 비즈니스 로직을 분리하여 코드의 가독성과 유지보수성을 높였습니다. 이를 통해 화면 관련 로직과 비즈니스 로직의 독립성을 유지하여, 각 요소를 독립적으로 개선하거나 테스트할 수 있게 하였습니다.
  • Stub 객체를 활용한 단위 테스트: UseCase 인터페이스를 구현한 Stub 객체를 통해 단위 테스트가 용이하도록 설계하였습니다. 이를 통해 실제 데이터 소스 없이도 비즈니스 로직의 동작을 검증할 수 있습니다.
  • 비동기 처리: async/await 패턴을 활용해 비동기 작업을 처리하며, UI와 데이터 처리의 성능을 최적화하였습니다. 이로써 동기 작업에 비해 UI 응답성이 향상되었으며, 데이터 로드 지연에 따른 문제를 줄였습니다.

5.2 편지 관련 전체

<BackEnd>

  • Result<>를 통한 상태 관리: 성공과 실패 상태에 따라 view가 적절히 업데이트되도록 Result<>를 활용하여 구현하였습니다.
  • Error 처리: do-catch 구문을 활용해 다양한 에러 상황에 대한 처리 로직을 구현하였으며, ErrorType을 세분화하여 디버깅을 용이하게 하였습니다. 이를 통해 앱 안정성을 높이고 문제 발생 시 빠른 대응이 가능하도록 하였습니다.
  • Firestore와 비동기 작업: Firestore와의 데이터를 async/await로 처리하여 데이터 저장 및 로드 작업의 비동기 처리를 효율적으로 처리하였습니다. 비동기 작업 중 발생할 수 있는 UI 지연을 최소화하여 사용자 경험을 개선하였습니다.

5.3 편지 보관함

<BackEnd>

  • FireStore 데이터 최적화: 사용자 별로 Send/Received/ToMe 컬렉션을 생성하여 데이터 검색 속도를 향상시켰습니다. 이를 통해 Firestore에서 데이터를 효율적으로 관리하고, 탐색 속도를 최적화했습니다.
  • Firestore CRUD 작업: Firestore에서 문서를 불러오고 수정 및 삭제하는 기능을 제공하며, 이를 비동기적으로 처리하여 사용자가 원활하게 편지 데이터를 관리할 수 있도록 구현하였습니다.

<View>

  • Property Wrapper 활용: @EnvironmentObject 등의 Property Wrapper를 사용하여 데이터 간 상태를 적절히 유지하며, viewviewModel 간의 통신을 원활하게 유지했습니다.
  • 화면 사이즈 대응: UIScreen, GeometryReader 등을 사용해 기기 간의 화면 사이즈 차이를 맞추고, 다양한 해상도에서 일관된 UI를 제공하도록 구현했습니다.
  • PreferenceKey: PreferenceKey 프로토콜을 통해 뷰 내에서 변화하는 값을 추적하며, 이 값을 사용해 UI의 비율을 동적으로 조정할 수 있도록 하였습니다.
  • Kingfisher를 사용한 이미지 로드: Kingfisher 라이브러리를 사용하여 편지함 내 이미지 데이터를 캐시와 함께 효율적으로 불러왔으며, 성능 최적화도 함께 진행하였습니다.
  • Blur 효과 적용: UIKit의 Blur 효과를 활용하여 다양한 화면에서 부드러운 전환 및 흐리기 효과를 적용하여 UI를 향상시켰습니다.
  • ViewBuilder로 뷰 계층 구조 관리: ViewBuilder를 통해 복잡한 뷰 로직을 분리하고 재사용 가능한 컴포넌트로 구성하여 코드의 가독성과 유지보수성을 향상시켰습니다.

5.4 편지 쓰기

<BackEnd>

  • 편지 불러오기 및 쓰기 기능 분리: 편지 불러오기와 쓰기 기능을 명확히 분리하여, 각각의 기능에 필요한 파라미터만을 전달하도록 하였습니다. 이를 통해 불필요한 데이터 전달을 줄이고, 각 기능이 독립적으로 동작할 수 있게 하였습니다.
  • 이미지 저장 및 로드: FireStorage를 활용하여 이미지 컨텐츠를 Firestore에 URL 형태로 저장하고, 필요한 경우 로드하는 구조를 구현하였습니다.
  • 편지지 및 우표 로딩: Firebase Storage에서 편지지, 우표, 편지봉투의 이미지를 로딩하고, 이를 사용자가 선택할 수 있도록 하였습니다.

<편지 불러오기>

  • PhotosPickerItem을 통한 이미지 선택: PhotosPickerItem을 사용해 사용자가 앨범에서 여러 이미지를 선택할 수 있는 기능을 구현했습니다.
  • UIImagePickerController를 통한 사진 촬영: UIImagePickerController를 활용해 사용자가 카메라로 사진을 촬영할 수 있는 기능을 구현하였으며, 촬영된 이미지는 Data 형식으로 변환되어 저장됩니다.
  • ZStack과 GeometryReader를 활용한 이미지 레이아웃: 선택된 이미지를 ZStackGeometryReader를 사용하여 중첩된 형태로 표시하는 UI를 구현하였습니다. 이를 통해 직관적이고 시각적으로 매력적인 이미지 배열을 제공하였습니다.
  • TabView로 구현된 이미지 갤러리: 선택된 이미지를 페이징할 수 있도록 TabView를 사용하여 이미지 갤러리를 구현했습니다. 사용자는 이미지 상세 보기를 통해 각 이미지를 페이지별로 열람할 수 있습니다.
  • 비동기 이미지 로드: async/await를 활용하여 선택된 이미지들을 비동기적으로 로드하고, 로딩 후에 UI가 즉시 업데이트되도록 처리하였습니다. 이를 통해 이미지 로드 지연을 최소화하였습니다.
  • 데이터 소스 구분: dataSource를 활용하여 편지 데이터가 불러온 편지인지 직접 작성한 편지인지 구분하고, 각 데이터 흐름에 맞는 로직을 구현하였습니다.

<편지 쓰기>

  • 사용자 로그인 상태 구분: 로그인 상태와 비로그인 상태에 따라 각각 다른 편지 쓰기 화면을 제공하며, 비로그인 상태에서는 제한된 기능을 제공하여 로그인 유도를 자연스럽게 유도하였습니다.
  • Firebase Storage에 저장된 이미지 불러오기: 사용자가 선택한 편지지, 봉투, 우표 이미지를 Firebase Storage에서 불러오며, 이미지가 뷰에 도달하기 전에 미리 로딩하여 사용자 경험을 개선했습니다.
  • 커스텀 텍스트 에디터: 텍스트 입력 시 가로 및 세로 너비 제한이 있는 텍스트 에디터가 필요하여 UIKit을 기반으로 커스텀 텍스트 에디터를 구현했습니다. SwiftUI의 한계를 보완하여 더 세밀한 UI 제어를 가능하게 하였습니다.
  • Combine의 debounce 기능 활용: 사용자 검색 입력이 멈춘 후 일정 시간(1초) 뒤에 검색이 실행되도록 Combinedebounce 기능을 활용하여 불필요한 요청을 줄이고, 검색의 효율성을 높였습니다.
  • 실시간 데이터 동기화: SwiftUI의 @Binding을 사용하여 편지 봉투와 우표의 URL을 뷰 모델과 실시간으로 동기화하여, 사용자가 선택한 옵션이 즉시 UI에 반영되도록 구현하였습니다.

5.5 프로필 관리

  • Firebase 익명 로그인: Firebase의 익명 로그인 기능을 사용하여, 사용자가 로그인을 하지 않아도 앱의 일부 기능을 사용할 수 있도록 구현하였습니다.
  • Apple Login과 ASAuthorization: Apple의 로그인 기능을 활용하여 ASAuthorization을 통해 사용자에게 간편한 회원가입 및 로그인 절차를 제공하였습니다.
  • Combine을 통한 비동기 데이터 관리: Combine 프레임워크의 AnyPublisher를 사용하여 비동기적으로 데이터를 관리하며, 사용자의 상태 변화가 실시간으로 반영될 수 있도록 구현하였습니다.
  • Resizable한 이미지 크롭 기능: 사용자가 프로필 이미지를 업로드할 때 크롭할 수 있도록 Resizable 크롭 박스를 구현하여 이미지의 크기와 위치를 조정할 수 있게 하였습니다.
  • LicensePlist를 통한 오픈소스 라이선스 관리: LicensePlist 도구를 사용하여 앱에 사용된 오픈소스 라이브러리들의 라이선스 정보를 자동으로 생성하고 표시할 수 있도록 하였습니다.

6. 기술적 챌린징

무엇이 어려웠읍니까? 어떤 것을 노력하였읍니까? 무엇을 배웠읍니까?

공통

  • 공동의 프로젝트를 처음 접하다보니 어떤 아키텍처가 가장 어울릴지 고민이 많이 되었다.
    SwiftUI는 선언형 UI이기 때문에 MVC보다는 MVVM의 모델이 더 어울릴 것으로 판단하였고, MVVM에서 의존성이 조금 덜할 수 있도록 약간의 Clean Architecture도 채택하게 되었다.

지혜:

  • (Firebase)
    Firebase라는 외부 라이브러리를 처음 사용하게 되어 막연함이 컸다.
    현재 서버를 구축할 수 있는 인력이 없기 때문에 Firebase를 서버로 사용하기로 하였다.
    Firebase를 처음 사용해보지만 효율적으로 활용하기 위해 공식 문서를 찬찬히 살펴보았고, 그 결과 크게 어렵지 않게 코드에 잘 녹여낼 수 있었다.

  • (Architecture)
    Service의 볼륨이 계속 커져만 가는 어려움을 겪었다.
    UseCase와 Service에 대한 개념이 와닿지 않아 개발할수록 Service의 볼륨이 커져만 갔다.
    초기 버전에서는 담당하게된 모든 UseCase를 Service에 밀어넣는 식의 개발을 하였고, 시간이 흘러 viewModel에서 사용하게 될 쯤 이 방식이 아니라는 걸 깨닫게 되었다.
    이후 리펙토링 과정을 통해 Service class의 볼륨을 덜어내는 작업을 진행하였고, 가독성도 좋아짐을 느꼈다. 이와 더불어 중복되는 객체 이식을 피할 수 있었다.

  • (Swift)
    에러 핸들링을 얼마나 자세히 해야할지 고민이 많이 되었다.
    개인적으로 에러 핸들링은 많을수록 좋다 생각하고 있다. 하지만 최대한 많은 경우에서 핸들링을 하려다보니 코드의 볼륨이 점점 늘어남을 느꼈다.
    view단에서 확실한 값들은 핸들링에서 제외하였고, 대신 logger를 적극 활용하여 디버깅이 용이할 수 있도록 하였다.
    변수명, 함수명을 결정하기 어려웠다.
    변수명과 함수명은 제 3자가 보아도 코드의 내용이 이해가 되어야한다고 생각한다.
    하지만, 개발을 하다 보니 "나" 기준 이해하기 쉬운 명명을 하게 되었고, 변수간 통일성도 떨어지는 것을 알게 되었다.
    팀원이 이 부분을 지적해주어 인지하게 되었고, 코드를 다시 살펴보며 제 3자의 이해가 편하게 변경하도록 노력하였다.

송:

  • (Architecture)
    처음 ViewModel과 UseCase 개념만 대략적으로 알고 프로젝트를 시작하면서, 뷰와 뷰모델을 어떻게 분리해야 할지 많은 고민을 했습니다. 또한 UseCase를 어떻게 적용해야 할지 어려웠습니다. 하지만 프로젝트를 진행하면서 이러한 구조를 실제로 적용해보면서, 점점 더 아키텍처에 익숙해질 수 있었습니다. 이를 통해 뷰와 뷰모델 간의 책임을 명확히 분리하고, UseCase를 통해 비즈니스 로직을 효율적으로 관리하는 방법을 배웠습니다.

  • (다른 팀원의 뷰 사용하기)
    다른 팀원이 작성한 뷰를 불러와서 사용하는 과정에서도 많은 도전이 있었습니다. 구현 방식이 제 방식과 달라서 처음엔 코드를 이해하는 데 시간이 많이 걸렸고, 오류가 발생했을 때 그 원인을 찾는 것도 쉽지 않았습니다. 하지만 팀원들과 협력하고 그들의 설명을 통해 문제를 해결할 수 있었고, 이를 통해 다른 사람과 협력하는 데 있어서 더 나은 소통 능력과 이해도를 쌓을 수 있었습니다.

  • (뷰에서 발생한 오류들)
    뷰 전환 시 데이터가 초기화되거나, 빈 화면이 출력되는 문제를 해결하는 데 많은 노력이 필요했습니다. 데이터를 불러오는 데 시간이 걸릴 때는 로딩 상태를 적절히 처리하고, 빈 화면을 방지하기 위해 초기값을 설정하는 등의 예외 처리를 추가했습니다. 이러한 문제를 해결하면서 여러 가지 경우의 수를 고려해 더 견고한 코드를 작성하는 습관을 기를 수 있었습니다.

  • (디자인과 뷰의 차이)
    Figma에서 디자인한 뷰와 실제로 구현된 뷰 간의 차이를 좁히는 것이 어려운 도전 과제였습니다. 최대한 디자인과 일치하도록 뷰를 여러 번 수정하고, 크기와 배치를 세부적으로 조정하면서 UI를 개선했습니다. 이를 통해 디자인에 맞춰 뷰를 세밀하게 구현하는 경험을 쌓을 수 있었습니다.

  • (UIKit 커스텀 텍스트 에디터)
    이번 프로젝트에서 가장 어려웠던 부분은 UIKit을 활용한 커스텀 텍스트 에디터 구현이었습니다. 편지지에 입력된 텍스트가 일정 길이를 넘어서면 새로운 편지지가 생성되고, 텍스트 삭제 시 편지지가 줄어드는 기능을 구현하려 했습니다. 텍스트의 가로 세로 길이가 폰트마다 달라서 간단한 계산으로 해결되지 않았고, 직접 텍스트의 길이를 계산해 제한을 주는 커스텀 로직을 작성했습니다. 현재는 텍스트가 일정 넓이를 넘기면 입력이 차단되도록 구현했으며, 추후 추가 기능을 통해 이를 더 발전시킬 계획입니다.

윤원:

  • (Planning)
    처음 프로젝트 일정을 세울 때, 맡은 업무를 이슈 단위로 쪼개고 Roadmap을 통해 예상 개발 기간을 산정했다. 하지만 실제 개발 기간과 예상 기간은 다를 수 있다며, 기간을 2-3배 넉넉하게 잡으라는 선생님의 조언을 뒤로 한 채 라이트하게 계획을 세웠다. 돌이켜보면, 이슈를 작성할 때 개발 과정에서 해야 할 세부 작업들을 더 세밀하게 나눠야 했는데, 그러지 못한 것이 큰 실수였다. 프로젝트 중반쯤에 이르러 문제 상황을 파악하고, 남은 작업을 더 작게 나누고 이를 바탕으로 Roadmap을 다시 산정한 덕분에, 중요한 과제들(함께 정한 MVP)을 기간 내에 수행할 수 있었다.

  • (Architecture)
    확장된 MVVM 아키텍처로 개발을 진행하면서, View와 ViewModel을 분리하는 작업과 서버와 데이터를 연동하는 과정에서 많은 어려움을 겪었다. 특히, 어떤 상황에서 ViewModel로 코드를 분리해야 할지, 현재 작성 중인 코드가 적절한 위치에 있는지에 대해 끊임없이 스스로 질문하며 구현을 이어나갔다. 코드를 작성할 때는 다른 개발자가 보았을 때도 이해하기 쉬운 코드가 되도록 네이밍에 신경을 썼으며, 복잡도를 낮추기 위한 구조적인 고민을 많이 했다. 특히, View가 지나치게 무거워지는 문제에 자주 부딪혔다. 이럴 때는 상황에 따라 로직을 ViewModel로 이동시키거나, @ViewBuilder를 활용하여 뷰의 로직을 분리하면서 성능과 유지보수성을 높이려고 노력했다. 또한, 재사용 가능성이 높은 코드는 따로 분리하여 모듈화하고, 결합도를 낮추기 위한 설계를 지속적으로 고민했다. 이를 위해 헬퍼 함수, 스텁, 익스텐션과 같은 로직을 분리하고, View 안에서도 컴포넌트를 나누어 폴더 구조를 체계화했다. 이러한 방식은 코드의 가독성을 높이고, 유지보수와 확장성을 고려한 구조를 갖추는 데 큰 도움이 되었다.

  • (Design)
    지금까지 내가 해본 디자인 경험이라고는 5개 이하의 화면을 그려본 것이 전부였다. 피그마를 사용해 앱의 UI를 그려본 적은 있지만, 그 또한 수많은 레퍼런스를 참고해 유사한 주제의 앱을 따라 그린 정도였다. 이번 프로젝트를 통해 처음으로 디자이너와 함께 0부터 100까지 디자인 과정을 함께했다. 전체적인 구상은 디자이너 율이 맡았고, 세세한 부분은 뷰를 개발하는 팀원들과 토의하며 프로젝트를 진행했다. 컴퓨터 전공 개발자들과 많은 프로젝트를 경험해왔기에, 나름 꼼꼼하게 뷰 내 디자인을 살피는 편이라고 자부했지만, 이번 경험을 통해 각 분야 전문가의 존재 이유를 절실하게 깨달았다. 컴퓨터 전공 친구들과 프로젝트를 할 때는 주로 기본 제공 컴포넌트 위주로 앱을 기획하며 현실적인 부분에 초점을 맞췄다면, 디자이너는 사용자 경험과 앱의 전체적인 무드에 더 집중했다. 덕분에 내가 미처 고려하지 못한 부분까지 깊이 있게 바라보며, 더 폭넓은 시야로 개발을 진행할 수 있었다. 가령, 기기마다 사이즈를 맞추는 방식이나, 단순한 TabView 대신 편지지를 실제로 겹쳐 보이게 하는 등 세부적인 부분에서 더 깊이 있는 고민을 하게 되었다. 이러한 경험 덕분에, 나 또한 한 명의 앱 유저로서 좋은 앱을 만들어가고 있다는 확신을 가지게 되었다.

  • (Responsive UI)
    앱의 메인 화면, 즉 우리 카비넷 앱의 얼굴을 그리는 작업을 맡았다. 지금까지 수업을 통해 그려본 UI보다 훨씬 다채롭고 복잡한 화면을 그려나갔다. 예를 들어, 편지는 두 버전으로 각각 다른 규격을 가지고 있었고, 나는 피그마에서 iPhone 15 Pro를 기준으로 고정된 값을 보고 뷰를 하나씩 그려나갔다. 그러다 문득, '더 작은 화면에서는 이들이 너무 크게 보일 텐데 어떻게 해야 할까?'라는 고민이 들었다. 이 문제를 해결하기 위해 선생님과 상의한 결과, 가장 큰 iPhone 15 Pro와 가장 작은 iPhone SE에서 화면이 잘리지 않고 보일 수 있도록 고정값을 설정하자는 결론을 내렸다. 그러나 실제로 적용해보니, iPhone SE에서는 올바르게 보였지만, iPhone 15 Pro에서는 화면이 너무 작아 보이는 문제가 발생했다. 이에 따라, 나는 다른 뷰에서 종종 사용했던 GeometryReader를 시도해보았다. 하지만 기대했던 대로 동작하지 않았고, 무엇보다 부모 뷰에 따라 새롭게 그려지는 것처럼 이상하게 보였다. 그래서 새로운 방안으로 UIScreen을 사용하게 되었다. 디자인에서 넘겨진 값들이 비율이 아닌 고정값이었기 때문에, 화면을 일일이 눈으로 확인하며 폰트와 프레임 사이즈까지 비율을 맞춰보는 작업이 필요했다. UIScreen을 사용하여 화면 비율을 조정하니 iPhone 15 Pro 시뮬레이터에서 정상적으로 작동했지만, iPhone SE에서 다시 비율이 깨지는 문제를 마주하게 되었다. 이때, 노치가 있는 핸드폰과 홈 인디케이터가 있는 핸드폰의 화면 비율이 생각보다 더 다르다는 것을 깨달았다. 그래서 앱 실행 시 디바이스 정보를 받아와, SE와 그렇지 않은 디바이스에 대해 각각 다른 화면 비율을 적용했다. 이번 프로젝트는 반응형 UI 작업을 본격적으로 해본 첫 경험이었으며, 어떤 화면에서도 동일하게 컴포넌트들이 그려질 수 있다는 점에서 매우 유의미한 작업이었다.

  • (Kingfisher)
    LazyVGrid 안에서 ForEach 문을 사용해 AsyncImage로 이미지를 비동기적으로 로드하는 과정에서 지속적으로 로딩 실패 이슈가 발생했다. 마지막으로 로딩된 데이터만 화면에 제대로 표시되었고, 나머지 데이터는 정상적으로 동작하지 않았다. 처음에는 뷰의 복잡한 구조 때문이라고 생각했다. 예를 들어, ZStack 안에 NavigationStack을 중첩하는 구조가 문제일 수 있다고 판단해 구조를 간결하게 변경해보기도 했지만, 문제는 여전히 해결되지 않았다. 결국 문제는 AsyncImage 자체에서 발생한다는 것을 알게 되었다. 특히, AsyncImage가 동일한 URL에 대해 이전 요청을 취소한다는 사실을 발견했다. 이로 인해 같은 이미지 URL을 사용하는 경우 이미지 로딩이 실패하는 상황이 반복되었다. 이 문제를 해결하기 위해 "캐시 기능을 지원하는 외부 라이브러리 Kingfisher"를 도입했다. Kingfisher를 사용하여 이미지를 안정적으로 비동기 로딩하고, 캐시를 활용해 성능도 개선할 수 있었다. 이를 통해 이미지가 정상적으로 로드되었고, 클릭 이벤트도 정상적으로 작동했다. 이미지 처리의 신뢰성이 확보되면서, 화면 구성도 제대로 이루어지게 되었다.

  • (Blur)
    블러 효과 구현은 이번 프로젝트에서 또 다른 도전이었다. 처음에는 SwiftUI에서 제공하는 blur와 opacity 모디파이어를 사용해 간단하게 구현하려고 했지만, 원하는 대로 동작하지 않았다. 다양한 뷰가 겹쳐보이는 구조 내에서 블러 처리와 투명도 조절이 예상치 못한 결과를 만들어냈고, 원하는 비주얼을 얻기 어려웠다. 이에 대한 해결책을 찾기 위해 UIKit을 활용한 커스텀 뷰를 도입했다. UIVisualEffectView를 사용해 블러 처리를 적용하면서, 불필요한 필터를 제거하는 TransparentBlurView를 직접 구현했다. 이 커스텀 뷰를 사용해 블러 효과의 세부적인 조정을 가능하게 하고, SwiftUI에서 원하는 디자인을 구현할 수 있었다. 아직 완벽하게 모든 상황에서 동작하는 코드는 아니지만, SwiftUI의 모디파이어로는 구현할 수 없었던 복잡한 블러 효과를 UIKit과의 혼합을 통해 어느 정도 해결할 수 있었다. 특히, 필터를 조정해 투명한 배경을 구현하는 방식으로, 복잡한 뷰 계층에서도 보다 자유로운 비주얼을 만들어 나가고 있다.

  • (UIKit+SwiftUI)
    수업 초반부터 SwiftUI와 UIKit 사이에서 고민이 계속되었다. UIKit이 자라면서 자연스럽게 Objective-C가 사라진 것처럼, SwiftUI가 성장하면 UIKit도 자연스럽게 사라질까 하는 생각이 떠올랐다. 이번 과정에서 두 가지 기술을 모두 배우긴 했지만, 한쪽에만 치우쳐 개발하는 것이 맞는지 여전히 고민스러웠다. 단순히 취업을 생각하면 UIKit이 유리할 것 같고, 최신 동향을 고려하면 SwiftUI로 마음이 기울었다. 하지만 이번 프로젝트를 진행하면서 한쪽에만 치우쳐 생각하기는 어렵다는 결론에 이르렀다. SwiftUI를 주언어로 선택해 개발을 진행했지만, 아직 부족한 부분이 많아 UIKit의 기능을 가져와 사용해야 하는 순간이 자주 있었다. 예를 들어, 블러 처리 같은 단순한 작업조차 SwiftUI의 모디파이어만으로는 자유롭게 구현하기 어려웠다. 지금처럼 SwiftUI와 UIKit이 혼용되는 과도기에는 두 기술을 유연하게 받아들이는 자세가 필요하다고 느꼈다. Swift라는 언어 속에서 필요한 기능을 찾아 적극적으로 활용하는 것이, 효율적인 프로그램 개발의 핵심임을 깨닫게 되었다.

정우:

  • (데이터 흐름 관리)
    ViewModel 간의 복잡한 데이터 흐름을 관리하는 데 어려움을 겪었습니다.
    특히 ImagePickerViewModel, CustomTabViewModel, EnvelopeStampSelectionViewModel 간의 데이터 공유와 동기화가 필요했습니다.
    이를 해결하기 위해 LetterWriteModel에 중앙 데이터 모델을 만들고, dataSource 열거형을 추가하여 편지 데이터의 출처(fromLetterWriting 또는 fromImagePicker)를 구분했습니다.
    이를 통해 각 ViewModel에서 일관된 데이터를 사용할 수 있게 되었고, 화면 전환 시 데이터 일관성을 유지할 수 있었습니다.

  • (비동기 이미지 처리)
    PhotosPickerItem에서 여러 이미지를 동시에 효율적으로 로드하는 과정이 필요했습니다.
    이를 위해 withThrowingTaskGroup을 사용하여 병렬 처리를 구현했습니다.
    구체적으로, loadImagesTask() 메서드에서 각 이미지 로딩을 별도의 task로 분리하여 동시에 처리하도록 했습니다.
    이 과정에서 메모리 사용량과 성능 사이의 균형을 맞추는 것에 중점을 두었습니다.

  • (동적 UI 구현)
    디바이스의 안전 영역을 고려한 동적인 탭바 위치 조정이 필요했습니다.
    이를 해결하기 위해 GeometryReader와 safeAreaInsets를 활용했습니다.
    CustomTabBar 구조체에서 calculateYPosition 메서드를 구현하여 디바이스의 안전 영역에 따라 탭바의 위치를 동적으로 계산하도록 했습니다.
    이를 통해 다양한 iOS 디바이스에서 일관된 UI를 제공할 수 있었습니다.

  • (복잡한 뷰 전환 관리)
    여러 화면 간의 복잡한 전환 흐름을 관리하는 것이 필요했습니다.
    이를 해결하기 위해 NavigationStack, fullScreenCover, sheet 등을 적절히 조합하여 사용했습니다.
    특히 CustomTabView에서 각 탭에 대한 별도의 NavigationPath를 관리하여 각 탭의 네비게이션 상태를 독립적으로 유지할 수 있도록 구현했습니다.

  • (에러 처리와 사용자 피드백)
    비동기 작업에서의 에러 처리와 사용자에게 적절한 피드백을 제공하는 것이 필요했습니다.
    이를 위해 ImagePickerViewModel에 isLoading과 error 상태를 추가하고, 각 비동기 작업에서 이를 적절히 업데이트했습니다.
    예를 들어, saveImportingImage() 메서드에서 성공/실패에 따라 상태를 업데이트하고 사용자에게 피드백을 제공하도록 구현했습니다.

  • 위의 어려움들을 해결하면서, 복잡한 한 화면에 두 데이터 흐름을 가진 앱을 효율적으로 구현하는 능력을 향상시킬 수 있었습니다.

도율:

  • Usecase와 MVVM의 관계: 인터페이스와 구현체의 구분
    우리팀의 뷰쪽을 개발한 사람들이 활용한 아키텍처 구조는 아래와 같다.
    백쪽 개발을 하는 팀원과 화면을 개발하는 팀원이 어떤 데이터들을 주고 받을지를 중심으로 합의해서 유즈케이스를 짠다. → 유즈케이스를 채택하는 stub 객체를 만든다. 서버에서 넘어오 는 데이터를 임의로 가정해서 값을 넣고, 화면에 잘 나오는지, 화면이 의도대로 넘어오는지를 테스트한다. → 테스트가 끝났으면 stub 객체를 빼고 진짜 객체(서버)를 끼운다. → 진짜 데이터가 술술 들어오신다.
    이러한 아키텍처의 구조를 이해하고 직접 사용하기가 여간 쉽지가 않았다. 하지만 어느정도 익숙해지고 나서는 오히려 데이터쪽까지 복잡하게 고려하지 않게 하기 위한 방법이기도 하다는 것을 알았다. 신기했던 것은 아키텍처 구조가 발전하고 있고 짜기 나름이라는 것이다. MVC, MVVM, Clean Architecture 등 정해진 구조가 있고 그것을 따라야하는 건줄 알았는데 진웅님 이 클린 아키텍처를 약간 섞은 확장된 MVVM을 구상했다고 하셔서 필요에 따라 유동적이라는 것을 알고 굉장히 흥미로웠다.

  • Navigation 방식: 모달과 푸시등의 방식, 모달의 크기, 스택된 이후의 처리 등등등 … ( 프로토타입이 필요하다!
    네비게이션은 그냥 화면에서 다른 화면으로 넘어가는 아주 간단한 요소라고 생각했는데 그렇지가 않았다. 개발로도 그렇고 UX로도 그렇다. 개발면에서는, 원하는 방식대로 화면을 전환하기가 생각보다 쉽지 않았다. 내가 겪은 어려움은 이러하다. 프로필 탭으로 들어가면 로그인 버튼이 있는데, 로그인 과정은 3개의 뷰로 나누어져 있다. 3개의 뷰를 지나 필요한 데이터를 모으고 나서, 프로필 탭으로 다시 돌아가 사용자의 프로필을 완성한다. 그런데 나는 프로필탭 위에서 3개의 화면을 stack하여 띄운 뒤, 또 프로필을 만들어 그 위에 올리는 식으로 작업을 하였던 것이다. 그리하야 프로필탭 위에 4개의 뷰가 얹어져있었고, 프로필탭 좌상단에 뒤로가기 버튼이 생기는 이상현상이 발생됐다. 대수롭지 않게 생각하고 백버튼을 히든시켰는데, 거기서 로그아웃을 한다던가 회원탈퇴를 하면 의도치 않은 화면이 등장하는 기현상이 벌어졌다. 그 문제를 진웅님이 찾아주셨고, 3개의 뷰를 pop시켜 없앤다음 프로필탭으로 돌아가는 미션이 주어졌다. 어렵지 않은 작업일 수 있지만.. 필요한 데이터를 들고 원하는 방식으로 화면을 전환하는 것이 참 쉽지가 않았다.
    UX 적으로도, 화면이 넘어가는 방식은 참 중요했다. 푸시를 해야하는데 프레젠트로 띄우거나, 모달을 내리고 나서의 화면 전환이라던가, 모달과 뷰의 전환 등등, 화면의 목적과 중요도에 따라 방식을 나누고 사용자가 나도 모르게 다음 화면으로 넘어가게 되는 쾌적한 느낌을 주어야 하는데, 플로우가 갈라지거나 길어질때는 부자연스러워지기 일쑤였다. 너무 많은 뷰가 쌓였을때 돌아가는 경험이라던가, 작은 모달에서 로그인 뷰로의 전환같은, 중요도에 차이가 있는 뷰를 연결 지을 때 같은 플로우는 무의식중에 부자연스럽다→불편하다로 이어지기가 쉬웠다. 인터랙션에 관심이 많은 나로서는 화면전환은 너무 당연해서 별로 신경을 안쓰던 것인데, 이것이 기초 중에 기초라는 생각이 들었다.

  • 사용자의 상태에 따라 화면을 구분하여 보여주는 로직: 진웅님과 지혜님은 대단해요
    우리 앱은 로그인을 안한 사용자(익명유저라 하겠다)도 나에게 보내는 편지는 쓸 수 있도록 기획되었다. 그래서 익명유저와 로그인유저로 사용자의 상태가 나뉘는데, 로그인 플로우에서 보면 한가지 상태가 더 추가된다. 바로 회원가입을 하다가 이탈한 유저이다. 그래서 총 이 3가지의 사용자 상태에 따라 알맞은 로그인 플로우를 보여줘야하는 미션이 있었다. 애플로그인 ASAuthorization 으로 유저의 고유성을 판단한 뒤, 이메일 정보를 받는다. 그리고나서 이름과 카비넷 넘버(앱상에서 고유번호)를 선택하게 하면 회원가입 이 성공한다. 이미 회원가입을 마친 유저는 로그인 버튼을 눌렀을때 ASAuthorization이 서버에 등록되어있는지 판별하고 되어있으면 프로필을 불러온다. 애플로그인까지만 하고 회원가입 과정 중 이탈한 유저는 서버에 ASAuthorization만 남아있으므로 이름을 입력하는 뷰부터 다시 보여준다. 이 과정을 모두 Combine으로 구현하여 유저 상태를 실시간으로 판별한다. 로그인 과정 하나인데 이렇게나 복잡한 구조를 가질 수 있는지 몰랐다. 상태에 따라 받아야하는 정보가 다르고, 판단해야하는 것도 다르고, 보여주는 화면도 달라야한다니.. 로그인 화면이 적어서 이 파트를 선택한건데, (빨리 끝내고 디자인 더 하려고) 완전히 빙산의 일각. 몇개 안되는 화면 뒤에서 해야하는 일들은 어마어마했다. 프론트엔드와 백엔드의 중간에 서있어보니 눈에 보이는 것이 전부가 아니라는 진부한 말이 뼈에 사무쳤다.. 눈에 안보이는 구조를 설계하고 고민하는 진웅님과 지혜님이 대단하게 느껴졌다.

  • 패딩과 온갖 컴포넌트들의 크기를 숫자로 넣지 않는 법!? 도율짱의 얼렁뚱땅 실험실~
    UIScreen, GeometryReader, 고정값의 비교분석 (iPhone 15proiPhone SE 를 비교하며)
    우리 앱의 페르소나는 채윤원이다. 나 혼자 그렇게 생각하고 있다. 그리고 내 친구인 고미미도 있다. 이 둘의 공통점은 아이폰 SE를 쓴다는 사실이다. SE는 작고.. 예쁘고.. 작다… 그래 지금으로선 가장 큰 iPhone 15pro와 가장 작은 iPhone SE에서 모두 예쁘고 쾌적하게 돌아가는 앱. 이어야한다고 생각했다. 디자인은 가장 최신 기종인 iPhone 15pro를 기준으로 하고 첫 구현도 15pro로한 다음, SE로 돌려보았더니 화면이 혁명적으로 잘려나갔다. 탭바가 중간부터 사라져있었다. (아마 탭바는 safeArea가 이그노어되어 생긴 문제로 보인다)상수로 구현된 상태에서 GeometryReaderUIScreen을 비교해가며 어떤 것이 고정값으로 구현된 것과 큰 차이가 없으면서, 화면이 작아졌을때 레이아웃이 깨지지 않고 그대로 바뀌는지 실험해봤다. 우선, GeometryReaderUIScreen을 설명하자면,
    GeometryReader는 부모뷰를 기준으로한다. 그래서 만약에 탭바가 있어서 탭바 엄마가 있고 그냥 화면 엄마가 있다고 했을 때, 탭바를 뺀 화면을 기준으로만 계산을 한다. 주변에 있는 부모들을 의식하기 때문에 여러 부모들과 함께 움직여야한다던가 하는 상황에서는 GeometryReader가 낫다. 반면, UIScreen은 디바이스의 물리적 화면 크기를 기준으로 한다. 다른 부모가 있든 없든 탭바가 있든 없든 실제 화면 사이즈를 기준으로 계산한다. 그래서 탭바가 있다는 가정하에 같은 크기의 원을 구현해보면 UIScreen이 좀더 크다. 그런데 문제가 있었다. GeometryReader를 쓰자니 디자인이 단순한 뷰여서 화면 중앙에 두는게 이쁜데 탭바를 고려해버려 위로 올라간다는 문제가 있었고, UIScreen를 쓰자니 15proSE의 화면 레이아웃이 달라졌다.(증거는 피그마에 있음) 고정값으로 넣었을때 SE에서 컴포넌트들의 크기가 전체적으로 커진다는 문제가 있었지만 레이아웃이 깨지는 것보다 낫다는 결론에 다다랐다. 그리고나서 든 생각은,
    작은 화면에 맞게 컴포넌트가 작아지지만 레이아웃을 깨트릴 것이냐, 화면에 비해 컴포넌트가 크지만 레이아웃을 지킬 것이냐 하는 문제로 변모하였다. 나의 선택은 후자였기에 결국 고정값으로 넣는 방법을 선택하였다. 다만 좌우 패딩은 UIScreen을 쓰는 것이 레이아웃이 더 적절했기때문에 좌우 패딩값만 UIScreen을 사용하는 것으로 !… 왜 다른것인지.. 기기마다 해상도가 다르기 때문일지? 연구해야하는 문제이다.

  • 피그마와 시뮬레이터, 실기기 간의 차이.. 도대체 왜 다 다른건데!!
    디자인하고 구현하고보니 시뮬레이터와 피그마가 다른 단위를 쓰고 있다고 느껴졌다. 똑같은 폰트 크기, 폰트 웨이트, 간격인데 왜 다르지? 하지만 분명히 달랐다.. 그런데 시뮬레이터와 실기를 비교해보니 또 달랐다 ! 다만 피그마와 시뮬레이터는 단위부터 달라서 수치적으로 오차가 있는 것으로 보이고, 시뮬레이터와 실기는 물리적 화면의 차이 때문으로 느껴졌다. 해결은 아주 정공법으로 하였다. 시뮬레이터를 보고 시뮬레이터 기준으로 다시 코드로 디자인하고, 실기로 보고 실기 기준으로 다시 코드로 디자인하였다. 엑스코드와 시뮬레이터, 실기의 단위 차이에 대해서 공부해보고 싶어졌다.

  • 크롭박스 : 나중에 우표에서 구현해야할 수도 / 이미지 확장자가 다르면 뒤집어지는 오류
    프로필 이미지를 PHPicker로 선택하고 원형으로 크롭하게 하기 위해 크롭 기능을 구현해야했다. 모디파이어같은게 있을줄 알았는데 생각보다 완전히 처음부터 만들어야했다. 드래그 제스처를 통해 크롭 박스의 모서리를 추적하여 위치와 크기를 파악해야했다. 인터넷 검색과 챗지피티를 통해 어려운 수식도 금방 쓸 수 있기는 했다. 그런데 아직 해결 못한 오류는, 특정한 이미지는 각도가 뒤집어지거나 90도가 돌아간채로 출력된다는 것이다. HEIC 확장자에 문제가 있을 것이라고 추측하고 있는데,

  • 뷰모델에 비즈니스 로직을 잘 분리하기 (앱이 빨라진다~~

진웅

7. 느낀 점

지혜

swift로 진행해보는 첫 프로젝트라 설렘 반, 두려움 반이었다. 아직은 많이 부족하다는 것을 뼈저리게 느끼고 있었고, 나의 실력으로 프로젝트를 진행해도 될지 위축감도 많이 들었다.
하지만 모두가 처음이기에 이 두려움은 잠시 미뤄두고 적극적인 마음으로 진행해보고자 마음을 많이 다잡았던 것 같다.
매일 진행되는 스크럼, 주에 한 번씩 진행되는 스프린트와 회고는 팀의 방향성을 알아보고 잡아가는데에 큰 도움이 되었다. 팀원들의 진행상황을 공유하며 나의 진행상황이 늦거나 빠르지 않은지 체크해볼 수 있었고, 회고를 통해 지난 일주일간의 K(eep)T(ry)P(roblem)를 이야기하며 팀 전반적인 분위기와 방향성을 잡을 수 있었다. 또한 나의 상황도 되돌아보며 고쳐야할 점, 잘하고 있는 점이 체크가 돼서 나 자신이 성장할 수 있는 소중한 시간이었다.

view단보다 firebase 서버와 연동이 되는 부분을 집중적으로 맡게 되었는데, 화면이 없다보니 처음에 테스트를 진행하려할 때 애를 많이 먹었다. 덕분에 임시 뷰를 만들어보기, XCTest 활용하기의 방법을 시도해볼 수 있었고, 자연스럽게 테스트에 대한 숙련도가 높아졌다.
Firebase 또한 처음 사용해보기 때문에 초기 장벽이 느껴졌는데, 공식 문서를 차근차근 읽어본 뒤 코드에 적용해보니 큰 문제 없이 적용이 잘 돼서 굉장히 뿌듯했던 기억도 떠오른다.

예상한 기간보다 개발이 빨리 끝났고, 다른 팀원들이 짠 코드들을 보며 내가 짠 코드가 굉장히 비효율적이라는 것을 느끼게 되었다.
예를 들어 FirestoreManager라는 class는 Firestore와의 직접적 연관이 있어야하는데, 이곳에 비즈니스 로직을 전부 담아둔 것을 알게 되었다. 이해력이 부족하던 개발 초기에 얼렁뚱땅 코드를 작성하기에 급급했던 것이 화가 되어 돌아온 것이다.
그리하여 리팩토링 작업을 시작하게 되었고 이 작업은 생각보다 공수가 들어갔다. 내가 한 곳에 코드를 담아둔 탓에 나의 코드를 통해 구성되는 viewModel에도 어느정도 영향이 가는 상황이었고 말 그대로 식은땀이 나는 기분이었다. 하지만 쾌적한 코드 구성을 위해 해야만 했다.
UseCase와 강한 결합도를 가진 Service 코드를 하나씩 분리하였고, 에러도 차근차근 잡아나갔다. 이왕 하는 리팩토링 더 잘하고 싶어서 생각을 굉장히 많이 하게 되었고, 어떻게 하면 될지 감도 잡혀가기 시작했다. 생각해 둔 것들을 바로 잡아가는 것이 너무 재미있게 느껴졌고, 역시 개발은 이 맛이지 싶었다!

우리 팀에는 디자이너가 있어 디자이너와의 협업이 어떻게 이루어질지도 가볍게나마 살펴볼 수 있어 많은 도움이 되었다. 예쁜 앱을 만들 수 있다는 건 덤이고!
시간적 여유가 많은 편은 아니었지만 MVP에 대해 각자 맡은 업무를 기간 내에 어떻게든 끝내는 팀원들의 모습을 보고 자극도 많이 받았다. 열정적인 사람들과 함께 있으면 나도 모르게 열정적으로 되는건 어쩔 수 없나보다.

테스트플라이트를 통해 처음 앱을 열어보았을 때, 완성된 버전이 아님에도 불구하고 엄청 행복했다. 모두가 함께 일궈낸 결과물이라 생각하니 팀원들에게 큰 감사함을 느낄 수 있는 시간이었다. 아직 부족한 부분이 많고, 더 추가하고 싶은 기능도 있지만 기간이 정해진 프로젝트인만큼 MVP를 충족한 우리의 앱이 무사히 세상 밖에 태어날 수 있기를 바란다.

프로젝트를 처음 시작할 때는 다른 사람들에 비해 지식이 부족하다고 느꼈고, 복잡한 선택의 순간 내 의견을 내기보다는 나보다 더 잘하는 사람들의 의견을 따르는 게 맞을 것이라는 안일한 생각을 가지고 있었던거 같다. 그러다 진웅님께서 자신의 의견을 내지 않고 끌려가기만 하는 것은 의미가 없다 말씀을 해주셨고, 그 말을 듣고 나서야 정신이 번쩍 들어 이후로는 주체적으로 공부하고, 더 적극적으로 제 의견을 내려고 노력하게 되었다.

초기 아키텍처 작업부터 Git을 사용하는 방법까지 익숙하지 않은 부분이 많았고, 그 과정에서 팀원분들께 자주 질문하고 혼자서도 많은 공부를 해야 했다. 처음 PR을 올리고 다른 사람들의 PR을 검토하고 어푸르브를 해주는 간단한 과정조차 나에게는 큰 도전이었고, 많은 걸 배울 수 있는 기회였다. 또한, 디자이너의 섬세한 피드백을 반영하며 작업을 수정하는 과정도 매우 의미 있는 경험이었다. 혼자 프로젝트를 진행할 때는 겪지 못했던 다양한 상황들을 경험하면서 많은 성장을 할 수 있었던 것 같다.

코딩을 하면서 맡은 부분들을 완벽하게 구현하고 싶다는 마음이 강했고, 이를 위해 많은 시간을 쏟았다. 한 번 완성한 코드를 여러 번 다시 보며 파일을 나누고, 불필요한 코드를 정리하는 과정에서 제 코드에 대한 애정도 점점 커졌다. 예전에는 다른 사람이 제 코드를 보고 "왜 이렇게 했어?"라고 물었을 때 자신 있게 설명하지 못하는 경우가 있었어서, 이번 프로젝트에서는 코드 작성에 있어 항상 명확한 이유를 생각하고 설명할 수 있도록 노력했던거 같다.

매일 진행된 데일리 스크럼과 매주 이루어진 회고, 그리고 하루가 끝날 때마다 작업 내용을 팀원들과 공유하는 과정은 나에게 큰 동기부여가 되었다. 다른 사람들이 얼마나 열심히 작업하고 있는지를 확인하면서 나도 게으르지 않도록 스스로를 다잡았고, 덕분에 끝까지 집중력을 유지할 수 있었다. 프로젝트를 함께한 팀원들에게 정말 감사하고, 해결해야 할 부분들을 끝마추고 완성된 앱을 빨리 출시해보고 싶다는 생각이다 .

윤원

느낀 점을 쓰려고 하니, 4주(대략 30일간)의 감정이 우수수 쏟아져 내려서 어디서부터 시작해야 할지 모르겠다. ✍🏻 팀 단위 작업은 2년 전 회사에서 인턴십을 하며 해본 것이 마지막이었고, 그 뒤로는 완전히 새로운 일에 도전하겠다고 혼자 세상을 구축해왔다. 누군가와 의견을 맞추고, 분량을 나누어 작업하며, 생각의 합일점을 찾아가는 일은 참 오랜만이었다. 그만큼 초반에는 (특히 기획 단계여서 그랬을지도 모르지만) 팀원 각자의 의견과 생각이 깊이 들어와서 감정적으로 몹시 지쳤고, 내 생각을 정리하는 일도 어려웠다. 동시에 이번 앱스쿨과 프로젝트는 꼭 잘 마무리하고 싶다는 마음이 컸기 때문에, 흔들리는 마음을 붙들고 여러 번 다시 일어나곤 했다.

이번 앱스쿨의 목표였던 '스위프트와 친해지기'는 아직 충분히 달성하지 못한 상태라, 내 의견을 피력하기에 자신이 없었는데, 진웅 고문님이 기획 단계부터 계속해서 "어떠한 행위에도 내 생각이 들어가 있어야 한다"라고 강조해 주신 덕분에 스스로 더 강해질 수 있었다. 사진 작업을 할 때에도 이 말을 계속 되새기곤 했는데, 코드를 작성할 때에도 이 원칙이 적용된다는 사실은 나에게 약간은 충격적이었다. 졸업작품을 할 때조차 '왜 이렇게 했지?'라는 질문을 이렇게 깊게 생각해본 적이 없었다. 하지만 이번 카비넷 프로젝트를 진행하면서 내내 "왜?"에 집중하게 되었고, 스위프트 개념이 부족한 상황에서도 내가 작성한 코드에 대한 책임을 다하려고 노력했다.

신기하게도 누군가의 지시로 작성한 코드가 아닌, 내 주관을 가지고 작성한 코드에는 자신감이 생겼고, 오랫동안 그 코드를 깊이 들여다보는 일도 전만큼 힘들지 않았다. 그리고 함께 만든 앱에는 모두의 애정이 담겨있음을 느꼈다. 짧은 기간 동안 0부터 100까지 함께 쌓아 올리는 작업을 하면서 다들 힘들었을 텐데, 늦장 부리거나 힘든 내색 크게 하지 않고 의견을 나누었기에 여기까지 올 수 있었다. 놀라울 정도로 공평한 업무 분담 덕에, 맡은 업무의 비중이 동일한 만큼 서로에게 기대는 일이 어려웠다. 그래서 중간중간 서로 힘든 점을 털어놓을 시간을 마련하고자 했고(매일 5시 45분 진척도 보고 등), 그 과정을 통해 모두가 조금씩 마음의 짐을 덜고 작업할 수 있기를 바랐다.

테스트플라이트에 우리 앱을 올리고, 내 핸드폰에서 앱이 동작하는 모습을 보며 몽글한 감정이 피어오르는 걸 느꼈다. 아직 해결해야 할 부분이 조금 남았지만, 무사히 잘 마무리하고 버전 1.0을 배포하는 날이 얼른 찾아왔으면 좋겠다 💌

정우

처음 시작할 때는 Swift와 iOS 개발에 대한 두려움과 설렘이 공존했습니다. 팀 프로젝트를 통해 처음으로 실제 앱 개발 과정을 경험하면서, 초기에는 부족함을 많이 느꼈지만 동시에 이를 극복하고자 하는 강한 의지도 생겼습니다.
데일리 스크럼과 주간 스프린트, 그리고 회고를 통해 팀의 방향성을 잡고 서로의 진행 상황을 공유하는 과정은 매우 유익했습니다. 이를 통해 개인의 성장뿐만 아니라 팀 전체의 발전을 도모할 수 있었고, 협업의 중요성을 깨달았습니다.
Firebase와 같은 기술을 적용하는 과정에서 초기에는 어려움을 겪었지만, 공식 문서와 자료를 읽고 적용해나가며 극복할 수 있었습니다. 이 과정에서 문제 해결 능력과 새로운 기술을 습득하는 능력이 향상되었음을 느꼈습니다.
코드 리팩토링을 통해 더 효율적이고 깔끔한 코드를 작성하는 방법을 배웠고, 이 과정에서 코드 구조화의 중요성을 깨달았습니다. 또한, 자신의 코드에 대해 명확하게 설명할 수 있는 능력의 중요성도 인식하게 되었습니다.
디자이너와의 협업을 통해 UI/UX의 중요성을 체감했고, 실제 사용자 경험을 고려한 개발의 필요성을 느꼈습니다.
팀원들과의 지속적인 소통과 피드백 교환은 프로젝트의 질을 높이는 데 큰 도움이 되었습니다. 특히, 다른 사람의 코드를 리뷰하고 자신의 코드에 대해 설명하는 과정에서 많은 것을 배울 수 있었습니다.
마지막으로, 테스트플라이트를 통해 실제로 동작하는 앱을 확인했을 때의 성취감은 말로 표현할 수 없을 정도로 컸습니다. 이 경험을 통해 앱 개발에 대한 열정이 더욱 커졌고, 앞으로도 계속해서 발전해 나가고 싶다는 강한 의지가 생겼습니다.
이 프로젝트를 통해 기술적인 성장뿐만 아니라 팀워크, 의사소통 능력, 문제 해결 능력 등 소프트 스킬도 크게 향상되었음을 느꼈습니다. 앞으로도 이러한 경험을 바탕으로 더 나은 개발자로 성장해 나가고 싶습니다.

도율

느낀 점이 너무 많은데, 정리가 잘 안되어요. 쓰고 싶은 주제들이랑 가공하지 못한 날 것의 생각들인데 주르르 열거해놓을게요! (급한 일들을 마치고 좀더 깊이 생각해서 줄글로 잘 정리해둘게요 ~)

- 무엇이 재미있었나?: 디자인할 때가 제일 재밌었다. 그리고 디자인이 잘 구현된 개발 결과물을 봤을 때가 재밌었다. 
- 어려웠던 것 : 개발 기초가 부족 → 만드는 것까진 어떻게 해도 디버깅, 수정, 확장이 어렵다. 너무너무.
- 피그마에서 디자인하는 감각과 코드로 디자인하는 감각이 달랐다.
- 협업의 어려움, 협업의 힘
- 신경쓴 것(디자인하면서 신경쓴 점, 이렇게까지 안해도 되는데 한 것들)
- 더 하고 싶은 것
- 구조와 설계와 뒷단의 중요성: 너무 버벅이니까 예쁜게 다 쓸데가 없다. 그냥 그만쓰고싶어진다.
- 개발할 줄 아는 디자이너?

디자인. 재밌었다. 뭐든 구현할 수 있다니!

개발. 너무 어려웠다. 만드는 것을 겨우겨우 해냈는데 수정과 확장이 어쩌면 더 많이 해야하는 일 같다. 기초가 부족하니 수정과 확장이 어렵다.

개발을 알기 때문에 가능한 것들: 피그마와 시뮬레이터가 다르고 시뮬레이터랑 실기가 또 달라서, 피그마 대로 구현을 해도 시뮬을 기준으로 디자인을 수정해야했다. 수정사항을 요청하고, 결과물을 받고, 또 피드백하는 것이 꽤나 큰 공수였다. 그래서 마지막쯤엔 간단한 것은 그냥 바로바로 코드로 수정했다. 훨씬 빠르긴 하였다.

결국 나는 디자이너가 되고 싶다. 움직이는 화면이 좋고 그걸 자유롭게 구현하는 것이 좋다. 결국 하고 싶은 것을 하려고하면 개발을 깊숙히 알아야하겠지만, 지금은 배운 것들로 만들어보고 싶은게 많아서 그런 것들부터 가볍게 해볼 생각이다.

움직이고 작동하는 것을 만들고 싶은 마음이 여기까지 오게했다. 배워보고나니, 정말 어렵다! 어려운데, 하고싶은 걸 할 수 있겠다는 걸 느꼈다. 그리고 돌아서면 다시 해보고싶은 마음으로 결국 돌아온다. 잘하고 싶은 게 있다는 마음은 너무나 강하다!

디자인보다는 개발이 더 오래 걸리고 구현이 까다로우니까 개발친화적으로 진행했다. 최대한 맞추려고 한 것 같다. 그래도 절대 안되는 것 (편지 스크롤, 편지를 한장만, 익명사용자 가능하게, 등등)만 고수했는데, 기획을 근거로 대는 것이 좀 깡패처럼 느껴졌다. 기획과 디자인에서 근거가 있지 않으면 개발이 공수가 크다보니 설득하기가 쉽지 않겠다는 생각도 들었다.

진웅

Clone this wiki locally