---
# System prepended metadata

title: "\U0001F5C2️ 프로젝트 매니저"
tags: [iOS, project, README, 프로젝트매니저, 그라운드룰, 커리어, 프젝매, Swift]

---

![](https://hackmd.io/_uploads/r1XRpaBkp.png)

# <img src = "https://hackmd.io/_uploads/B1r7f2cya.png" width = "35"> 프로젝트 매니저

## Ground Rules

### 📢 스크럼
- 오전 9시 30분에 디스코드에서 진행
- 잠깨기
- 금일 진행 사항 공유하기(오늘의 할일)

### 📖 프로젝트 규칙
- 네이밍 준수하기(가이드 라인)
- 브랜치
    - main 
    - 제출 - step1, step2
    - 우리가 작업할 - whales, minsup

- 코드에 대한 기록 그때그때 하기
- 중간중간 리드미 작성
- 타입 생성자로 생성된 프로퍼티는 타입 생략

### ✏️ 커밋 규칙 - 깃모지
- ✨ Feat: 기능 추가
- ♻️ Refactor: 기능과 관련된 개선/전면수정
- 🩹 Chore: 빌드 업무 수정, 패키지 관련 수정, 환경 설정 등
- 🐛 Fix: 오류 수정
- 💄 Style: 네이밍, 컨벤션, 포멧 수정
- 📝 Docs: 문서 추가 및 수정
- ✅ Test: 테스트 추가
- 커밋 단위: 기능 단위, chore 묶어서 커밋

### 👩🏻‍⚖️ 팀 규칙
- TIL, 일일 회고 작성 시간(매일 22시부터 1시간 작성 진행)
- 컨디션이 좋지 않을 때는 꼭 말해주기!!
- 방해금지시간 : 2시 - 9시

### 🔖 스케줄

- 월목
    | 시간 | 내용 | 상세 |
    | :--------: | :--------: | :--------: |
    | 09:30-10:00 | 스크럼 |  |
    | 10:00-12:00 | **활동학습 예습** |  |
    | 14:00-17:00 | **활동학습** |  |
    | 19:00-22:00 | 프로젝트 | 필요시 개인공부 |
    | 그 후 | TIL 및 개인시간      |  |

- 화수금
    | 시간 | 내용 | 상세 |
    | :--------: | :--------: | :--------: |
    | 09:30-10:00 | 스크럼 | |
    | 10:00-12:00 | 프로젝트 |  |
    | 14:00-17:00 | 프로젝트 및 개인공부 |  |
    | 19:00-22:00 | 개인공부 | 필요시 프로젝트 |
    | 그 후 | TIL 및 개인시간      |  |

- 예외사항
    - 야아톤: 10.5-6
    - 리뷰를 받으면 유동적으로!
    - 추석 목금 OffDay

## 일일 스크럼
### 🙌 09/18 월
- 오늘의 컨디션
    - Minsup🦊: 너무 힘든 하루...🫠
    - Whales🐬: 잘 자고 잘 쉬고 말짱합니다!!
- 특이사항
    - Minsup🦊: 없습니다!
    - Whales🐬: 컴바인 너무 어려워요..🤯
- 오늘 할 일
    - [x] 그라운드 룰
    - [x] 기술 스택 상의하기

### 🙌 09/19 화
- 오늘의 컨디션
    - Minsup🦊: 약간 피곤...?
    - Whales🐬: 안 졸립니다...?!
- 특이사항
    - Minsup🦊: 없습니다!
    - Whales🐬: 저도 없습니다. 무난무난한 하루!!
- 오늘 할 일
    - [x] 그린과 기술 스택 상의해서 정하기 (오후 2시)
    - [x] 목표 - STEP 1 PR 보내기

### 🙌 09/20 수
- 오늘의 컨디션
    - Minsup🦊: 상쾌하진 않지만..좋습니다.
    - Whales🐬: 푹 잘자서 아주 상쾌합니다.
- 특이사항
    - Minsup🦊: 모각코가 있습니다
    - Whales🐬: 역시 사람은 잠을 잘 자야합니다.
- 오늘 할 일
    - [x] SwiftUI 만져보기
    - [x] Step2 시작 2:30

### 🙌 09/21 목
- 오늘의 컨디션
    - Minsup🦊: 좋습니다.
    - Whales🐬: 오늘은 너무너무 졸립니다
- 특이사항
    - Minsup🦊: 없습니다.
    - Whales🐬: 열심히 공부해야합니다!!!
- 오늘 할 일
    - [x] 활동학습
    - [x] Step2 쪼개보기 (추가하는 부분까지가 2-1)
    - [x] Whales - 도전!! 혼자서 추가하는 것까지 구현해보기
    - [x] Minsup - 아키텍처 고민. 파일구조, 브랜치, 머지 등
    - [x] MVVM 적용

### 🙌 09/22 금
- 오늘의 컨디션
    - Minsup🦊: 잘 잤습니다~~
    - Whales🐬: 잠이 부족함이 느껴집니다아
- 특이사항
    - Minsup🦊: PR 도전
    - Whales🐬: 다시 스유 도전!! 으쌰으쌰!!
- 오늘 할 일
    - [x] Whales - Environment 키워드로 다시 도전!!
    - [x] README
    - [x] Minsup - Refactoring

### 🙌 09/25 월
- 오늘의 컨디션
    - Minsup🦊: 좋습니다.
    - Whales🐬: 몸이 으슬으슬 좋지 않습니다😓
- 특이사항
    - Minsup🦊: 없습니다.
    - Whales🐬: 하루를 날려먹었습니다아....🫠
- 오늘 할 일
    - [x] 활동학습

### 🙌 09/26 화
- 오늘의 컨디션
    - Minsup🦊: 좋습니다.
    - Whales🐬: 몸이 안 좋은건 괜찮은데 아주아주 피곤하네요
- 특이사항
    - Minsup🦊: 저녁약속
    - Whales🐬: 비가 옵니다....비가 와요오....
- 오늘 할 일
    - [x] 리뷰 받은 부분 수정

### 🙌 09/27 수
- 오늘의 컨디션
    - Minsup🦊: 좋습니다. 행복합니다.
    - Whales🐬: 늦잠....잤습니다?
- 특이사항
    - Minsup🦊: 없습니다.
    - Whales🐬: Async / Await...어려워요.....
- 오늘 할 일
    - [x] 활동학습
    - [x] Whales - 멘토링
    - [x] Minsup - 2차 피드백 반영

### 🙌 10/01 일
- 오늘의 컨디션
    - Minsup🦊: 좋습니다. 행복합니다.
    - Whales🐬: 푹 잘자고 아주 잘 쉬었습니다.
- 특이사항
    - Minsup🦊: 없습니다.
    - Whales🐬: 언니네 집 갔다 왔습니다 헤헿
- 오늘 할 일
    - [x] STEP 2-2 PR 보내기
    - [x] README 추가 작성

### 🙌 10/03 화
- 오늘의 컨디션
    - Minsup🦊: 좋습니다!!
    - Whales🐬: 아무래도 감기가 오려나 봅니다아아아
- 특이사항
    - Minsup🦊: 늦잠...
    - Whales🐬: 민섭 늦잠이시면 저도 좀 더 자고오겠습니다 하핳
- 오늘 할 일
    - [x] STEP 2-2 리뷰 받은 부분 수정하기
    - [x] STEP3 시작! - Realm 공부

### 🙌 10/04 수
- 오늘의 컨디션
    - Minsup🦊: 매우 졸립니다?
    - Whales🐬: 푹 잘자서 아주 개운합니다!!!
- 특이사항
    - Minsup🦊: 디자인 패턴 공부하고 발표준비를 해야합니다. 10시쯤?
    - Whales🐬: Realm 너무 어려워요.. MVVM 특강 기대됩니다!!!
- 오늘 할 일
    - [ ] Realm 적용
    - [ ] MVVM 특강
    - [ ] PR step3 도전(?)

### 🙌 10/05 목
- 오늘의 컨디션
    - Minsup🦊: 
    - Whales🐬: 오늘은 매우매우 피곤합니다아아아아아아
- 특이사항
    - Minsup🦊: 
    - Whales🐬: 디자인패턴 발표!! 떨립니다 후하후하
- 오늘 할 일
    - [ ] 디자인패턴 발표 준비
    - [ ] 활동학습 - 디자인패턴 발표
    - [ ] Whales - 야아톤 (오후 4:30~)
    - [ ] Minsup - STEP 3

### 🙌 10/06 금
- 오늘의 컨디션
    - Minsup🦊: 
    - Whales🐬: 
- 특이사항
    - Minsup🦊: 
    - Whales🐬: 
- 오늘 할 일
    - [ ] Whales - 야아톤 (~오후 4:30), 멘토링(밤 10~12시)
    - [ ] 
    - [ ] 

# <img src = "https://hackmd.io/_uploads/B1r7f2cya.png" width = "35"> 프로젝트 매니저  <img src = "https://hackmd.io/_uploads/Bkf_435kT.png" width = "30"><img src = "https://hackmd.io/_uploads/rJhCN3qkp.png" width = "30">


- 프로젝트 기간: [2023년 9월 18일 ~ 10월 6일](https://github.com/WhalesJin/ios-project-manager/wiki/타임라인)
- 프로젝트 팀원: [Whales <img src = "https://hackmd.io/_uploads/Bkf_435kT.png" width = "20">](https://github.com/WhalesJin) , [Minsup <img src = "https://hackmd.io/_uploads/rJhCN3qkp.png" width = "20">](https://github.com/agilestarskim)
- 프로젝트 리뷰어: [GREEN <img src = "https://hackmd.io/_uploads/H1pGWNoyp.png" width = "20">](https://github.com/GREENOVER)

---

## 📖 목차
🍀 [소개](#소개) </br>
💻 [실행 화면](#실행_화면) </br>
🛠️ [사용 기술](#사용_기술) </br>
👀 [다이어그램](#Diagram) </br>
🧨 [트러블 슈팅](#트러블_슈팅) </br>
📚 [참고 링크](#참고_링크) </br>
👩‍👧‍👧 [about TEAM](#about_TEAM) </br>

</br>

## 🍀 소개<a id="소개"></a>

<img src = "https://hackmd.io/_uploads/Bkf_435kT.png" width = "20"><img src = "https://hackmd.io/_uploads/rJhCN3qkp.png" width = "20"> : 하나의 프로젝트를 세분화하여 관리할 수 있는 어플리케이션입니다.
칸반보드를 사용하여 TODO, DOING, DONE으로 분류해 진행도를 한 눈에 확인할 수 있습니다.
각 셀을 길게 클릭하여 할 일의 상태를 변경할 수 있고 스와이프를 통해 삭제할 수 있습니다.
상단 바에 위치한 + 버튼을 눌러 새로운 셀을 추가하거나 기존의 셀을 클릭하여 수정할 수 있습니다.



</br>

## 💻 실행 화면<a id="실행_화면"></a>

| 기본 화면 |
| :--------: |
| <img src = "https://hackmd.io/_uploads/BkzTn1Pga.png" width = "600"> | 

| 새 할 일 추가 |
| :--------: |
| <img src = "https://hackmd.io/_uploads/rJC22ywgp.gif" width = "600"> |

| 기존 할 일 편집 |
| :--------: |
| <img src = "https://hackmd.io/_uploads/ByIcjJwlp.gif" width = "600"> | 

| 카테고리 변경 |
| :--------: |
| <img src = "https://hackmd.io/_uploads/r18qskPlT.gif" width = "600"> |


</br>

## 🛠️ 사용 기술<a id="사용_기술"></a>
| 구현 내용	| 도구 |
|:---:|:---:|
|UI|SwiftUI|
|아키텍쳐|클린 아키텍쳐|
|디자인패턴|MVVM|
|로컬 데이터|Realm|
|리모트 데이터|Firebase|

</br>

## 👀 Diagram<a id="Diagram"></a>
### 📐 UML

<img src = "https://hackmd.io/_uploads/HkGA5h9y6.png" width = "800">


</br>

# Separator

## 🧨 트러블 슈팅<a id="트러블_슈팅"></a>

### 1️⃣ 기술스택 설정

#### 1. 뷰 드로잉 - SwiftUI
UIKit에 비해 다음과 같은 장점을 느껴서 SwiftUI로 선택하였습니다.
 - 뷰 그리기 간단하다.
 - 레이아웃 많이 시간 안 뺏김
 - 코드가 짧고 직관적이다.
 - 재밌다.

#### 2. 아키텍처 - 클린 아키텍처 + MVVM
프로젝트들의 코드를 보면 체계도 없는 것 같고, 복잡성이 많이 얽혀있는 느낌이라 이렇게 중구난방하지않고 규칙이 있거나 의존방향이 일정했으면 좋겠다는 생각을 했습니다.
이를 해결해주는 것이 의존성 규칙에 대한 얘기를 하는 `클린 아키텍처`라고 생각합니다. 클린 아키텍처는 이론적이고 추상적인 느낌이라 방법이 되게 다양해 보였고, 그 중에서도 저희는 MVVM을 접목해서
**클린 아키텍처를 기반으로 MVVM**을 구현해보았습니다.

#### 3. 로컬데이터 - Realm
CoreData에 비해 다음과 같은 장점들이 더 와닿아서 Realm으로 결정했습니다.
- 속도가 CoreData보다 훨씬 빠르다.
- 직관적인 API를 제공하고 있어 사용성이 좋다.
- 외부 라이브러리긴 하지만 MongoDB가 관리하고 있어 신뢰성이 높다.
- 문서 정리가 잘 되어있고 커뮤니티가 잘 생성되어있어 정보 습득이 빠르다.
- SPM을 지원한다.
- Entity Mapping할 때 CoreData보다 훨씬 편하다.
-> CoreData는 객체 생성 시 context가 필요하다.

#### 4. 리모트데이터 - Firebase
iCloud와 Firebase 중에 고민이 되어 두 가지를 비교해보고 결론적으로 Firebase가 현재 프로젝트에는 더 좋을 것 같다고 판단해서 Firebase로 선택하였습니다.

<img src = "https://hackmd.io/_uploads/H18Ofyvyp.png" width = 80>

- 편의성이 좋다. CoreData와의 연동이 좋다.
코어데이터를 상속받은 클래스 사용
[NSPersistentCloudKitContainer](https://developer.apple.com/documentation/coredata/nspersistentcloudkitcontainer)
- iCloud는 제공이 약하다.
- iCloud 계정을 쓰므로 친구가 되어있으면 저장 위치에 따라 접근 수준을 정할 수 있다.
- 의존성 관리도구 필요없다.
- [CloudKit](https://developer.apple.com/kr/icloud/cloudkit/)

<img src = "https://hackmd.io/_uploads/SyrlGJDy6.png" width = 110>

- 안드로이드랑 같이 공유가 가능하다. (포트폴리오로 좋다)
- 단점은 비싸다.
- 의존성 관리도구 : 코코아팟이나 **SPM**
- 안드로이드 커버나 커리어 → Firebase
- 제공해주는 서비스가 많다. All in One
    - 데이터베이스랑 셋팅이 다 되어있다.
    - 보안 솔루션도 다 설치되어있다.
- 데이터 센터. 서울도 존재한다.
- 기본요건이 아래와 같아서 하위 버전 호환도 문제 없다.
    - Xcode 14.1 이상
    - iOS 11 이상
- 단점: 직접 DB에 접근하니까 보안이 약하다

### 2️⃣ CUSTOM MODIFIER
🚨 **문제점** <br>
Custom View를 alert 형태로 띄우기 위한 방법을 모색했습니다. 기본 API는 주어지지 않아 직접 구현을 해야했습니다.

💡 **해결방법** <br>
ZStack으로 배경과 alert을 나누고 그 사이에 검은 불투명 뷰를 넣어 alert이 띄어있는 효과를 만들었습니다. binding을 이용해 최대한 SwiftUI API와 비슷하게 만들었습니다.
<br>


### 3️⃣ ViewModel 선언 방식
🚨 **문제점** <br>
처음엔 아래와 같이 ViewModel을 각 View 안에 중첩타입으로 구현했습니다. 이 방법은 ViewModel이란 이름을 각 뷰에서 중복해서 사용할 수 있다는 장점이 있습니다. 하지만 View에서 다른 View의ViewModel에 접근하려면 뷰이름.뷰모델 이런식으로 접근해야한다는 단점이 있었습니다.

```swift
extension KanbanView {
    final class ViewModel: ObservableObject { ... }
}

struct KanbanView: View {
    @ObservedObject var vm = ViewModel()
    ...
}
```

💡 **해결방법** <br>


전역적으로 사용 가능하게끔 따로 빼내어 타입을 생성하였습니다.
```swift
final class KanbanViewModel: ObservableObject { }
```
<br>

### 4️⃣ TaskFormView 재활용

🚨 **문제점** <br>
TaskFormView에서 Create와 Edit을 구현하기 위해 중복된 부분들을 어떻게 처리할까 고민했습니다.


💡 **해결방법** <br>

1️⃣ View의 init에서 분기처리

view를 init할 때 optinal Task를 사용해, create일 경우 nil, edit일 경우 수정할 task를 주입하여 분기처리를 하였습니다. 단점은 init에 로직이 들어가 복잡해지고 기능이 추가될 때마다 수정할 부분도 많아지고 사용하지 않은 메소드들을 viewModel에 모두 집어넣어야한다는 문제가 있었습니다.

[Optional Task를 이용](https://github.com/WhalesJin/ios-project-manager/blob/f2d72324a03fabdb7f51d02ce3442bcc2aaa28e3/ProjectManager/ProjectManager/Presentation/Kanban/TaskForm/TaskFormView.swift)

2️⃣ Protocol을 이용해 구현하기

Create와 Edit을 관찰해보니 중복되는 부분이 많아서 프로토콜로 기능 정의 후 Create와 Edit의 ViewModel을 각각 따로 만들어서 뷰모델을 주입하는 형식으로 구현해봤습니다. 단점은 기능이 추가될 때마다 각 뷰모델에 똑같은 메소드와 프로퍼티를 억지로 만들어야한다는 단점이 있습니다.

[Protocol 사용](https://github.com/WhalesJin/ios-project-manager/blob/ddea938b958136009b59b3b63a54f7b339904648/ProjectManager/ProjectManager/Presentation/Kanban/TaskForm/TaskFormView.swift)

<br>


### 5️⃣  Move to 기능 구현하기
🚨 **문제점** <br>

기존 코드는 todos, doings, dones를 각각 분리된 배열로 구현되어있어서 한 배열에서 다른 배열로 변경하려면 기존 배열에서 remove하고 다른 배열에서 append 하는 방식으로 진행되어야 했습니다. 이렇게 짜게 되면 상태변화에 따른 메서드들의 중복이 늘어나 다른 방법을 고민해보았습니다.

```swift
func moveFromTodo(task: Task, to other: String) {
    if other == "DOING" {
        moveToDoingFromTodo(task: task)
    } else {
        moveToDoneFromTodo(task: task)
    }
}
    
func moveToDoingFromTodo(task: Task) {
    guard let index = todos.firstIndex(of: task) else { return }

    todos.remove(at: index)
    doings.append(task)
}
    
func moveToDoneFromTodo(task: Task) {
    guard let index = todos.firstIndex(of: task) else { return }

    todos.remove(at: index)
    dones.append(task)
}
```

💡 **해결방법** <br>

1️⃣ state 속성을 추가해 state를 기준으로 필터링하여 배열을 분리하였습니다.
이 방법은 실제로 요소를 다른 배열로 이동시키는 것이 아니라 상태만 바꿈으로서 이동하는 것처럼 보이게 할 수 있는 장점이 있습니다. 하지만 이 과정에서 원본 tasks 배열 순서대로 요소들이 보여지기 때문에 저희가 원하는 순서대로 보여지지 않았습니다.따라서 요소의 순서를 어떤 기준으로 보여주어야할지 기획해야했고 결국 date를 기준으로 정렬하여 문제를 해결하였습니다.

```swift
@Published var tasks: [Task]
    
var todos: [Task] {
    return tasks.filter { $0.state == .todo }
}

var doings: [Task] {
    return tasks.filter { $0.state == .doing }
}

var dones: [Task] {
    return tasks.filter { $0.state == .done }
}

init(tasks: [Task] = []) {
    self.tasks = tasks
    self.tasks.sort { $0.date < $1.date }
}

func create(_ task: Task) {        
    tasks.append(task)
    tasks.sort { $0.date < $1.date }
}
```


<br>

## 📚 참고 링크<a id="참고_링크"></a>

- <Img src = "https://hackmd.io/_uploads/Hyyrii91T.png" width="20"/> [Firebase](https://firebase.google.com/?hl=ko)
- <Img src = "https://hackmd.io/_uploads/B1pu3oq1a.png" width="20"/> [Realm 설치](https://www.mongodb.com/docs/realm/sdk/swift/install/#std-label-ios-install)
- [🍎 Apple Docs: CloudKit](https://developer.apple.com/kr/icloud/cloudkit/)
- [🍎 Apple Docs: List](https://developer.apple.com/documentation/swiftui/list)
- [🍎 Apple Docs: ViewModifier](https://developer.apple.com/documentation/swiftui/viewmodifier)
- [🍎 Apple Docs: GeometryReader](https://developer.apple.com/documentation/swiftui/geometryreader)
- [🍎 Apple Docs: ScrollViewReader](https://developer.apple.com/documentation/swiftui/scrollviewreader)
- [🍎 Apple Docs: UnitPoint](https://developer.apple.com/documentation/swiftui/unitpoint)
- [🍎 Apple Docs: ]()
- [🍏 WWDC: ]()
- [🍏 WWDC: ]()
- [🍏 WWDC: ]()
- [📚 StackOverflow: ViewModel Protocol](https://stackoverflow.com/questions/59503399/how-to-define-a-protocol-as-a-type-for-a-observedobject-property)
- <Img src = "https://github.com/mint3382/ios-calculator-app/assets/124643545/56986ab4-dc23-4e29-bdda-f00ec1db809b" width="20"/> [야곰닷넷: Test Double](https://yagom.net/courses/unit-test-작성하기/lessons/테스트를-위한-객체-만들기/topic/테스트를-위한-객체를-이용해서-테스트-작성하기/)
- <Img src = "https://hackmd.io/_uploads/ByTEsGUv3.png" width="20"/> [blog: URL 처리 방법](https://ios-development.tistory.com/1014)

<br>

---

## 👩‍👧‍👧 about TEAM<a id="about_TEAM"></a>

| <Img src = "https://hackmd.io/_uploads/r1elEh5ka.png" width="100"> | 🐬Whales🐬  | https://github.com/WhalesJin |
| :--------: | :--------: | :--------: |
| <Img src = "https://hackmd.io/_uploads/HkTfN2cyp.png" width="100"> | **Minsup** | **https://github.com/agilestarskim** |

- [타임라인 링크](https://github.com/WhalesJin/ios-project-manager/wiki/타임라인)

<details>
    
|날짜|내용|
|:--:|--|
|2023.09.18(월)| - 기술 스택 조사 및 결정 |
|2023.09.19(화)| - `SwiftUI`로 프로젝트 UI 변경 및 아이콘 추가 <br> - `Realm` 및 `Firebase` 패키지 추가 |
|2023.09.20(수)| - `클린아키텍처` + `MVVM` 구조 생성 <br> - `CellView`, `ListView`, `TodoView` 구현 |
|2023.09.21(목)| - `TaskAddView` 열고 닫기 구현 |
|2023.09.22(금)| - README 작성 <br> - `TaskAddView`를 `TaskFormView`로 수정 및 로직 구현 <br> - Column header에 카드 개수, 지난 날짜 빨간색으로 표시 등 UI 개선 |
|2023.09.26(화)| - `TaskFormView`의 UI 및 상세 로직 변경 <br> - `View`의 반복 `modifier`를 `custom Modifier`로 분리 <br> - 길게 눌러 상태 변경 및 스와이프 삭제 구현 |
|2023.09.27(수)| - 키보드로 인해 `TextEditor` 가리는 문제 해결 <br> - 2차 피드백 반영|
|2023.09.29(금)| - 카드 클릭 시 `Edit` 기능 추가 |
|2023.09.30(토)| - `separator` 간격 제거 |
</details>
    
</br>

### 👥 팀 회고<a id="팀_회고"></a>
- [팀 회고 링크]()

### 👏🏻 우리팀 잘한 점


### 💜 서로에게 좋았던 점 피드백
- Dear. Minsup
- Dear. Whales 🐋


----
