{"id":18060266,"url":"https://github.com/dev-junehee/me-time","last_synced_at":"2025-04-05T12:14:27.012Z","repository":{"id":259208958,"uuid":"855477997","full_name":"dev-junehee/me-time","owner":"dev-junehee","description":"하루에 하나 모닝페이퍼를 작성하며 자신의 생각과 감정을 기록하는 앱","archived":false,"fork":false,"pushed_at":"2024-10-22T09:27:27.000Z","size":4128,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-23T13:03:47.900Z","etag":null,"topics":["ios","realm","swift","swiftui"],"latest_commit_sha":null,"homepage":"https://apps.apple.com/kr/app/me-time-%EB%AF%B8-%ED%83%80%EC%9E%84/id6711330732","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dev-junehee.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-10T23:48:58.000Z","updated_at":"2024-10-22T09:27:32.000Z","dependencies_parsed_at":"2024-10-26T13:13:05.868Z","dependency_job_id":"4f8f5df6-0fa3-4d8c-966b-3b6c1aab7bcb","html_url":"https://github.com/dev-junehee/me-time","commit_stats":null,"previous_names":["dev-junehee/me-time"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-junehee%2Fme-time","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-junehee%2Fme-time/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-junehee%2Fme-time/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-junehee%2Fme-time/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dev-junehee","download_url":"https://codeload.github.com/dev-junehee/me-time/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332596,"owners_count":20921854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ios","realm","swift","swiftui"],"created_at":"2024-10-31T04:06:45.471Z","updated_at":"2025-04-05T12:14:26.986Z","avatar_url":"https://github.com/dev-junehee.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ⏳ 미타임 (Me-Time) - 오롯이 나를 알아가는 시간\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg width=180\" src=\"https://github.com/user-attachments/assets/2d1ee65b-9249-4873-a2b9-f1b9e96a6abf\" /\u003e\n  \u003cbr /\u003e\u003cbr /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Swift-v5.1-F05138?logo=swift\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Xcode-v15.4-147EFB?logo=Xcode\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/iOS-16.0+-000000?logo=apple\" /\u003e  \n  \u003cbr /\u003e\u003cbr /\u003e\n   \u003ca href=\"https://apps.apple.com/kr/app/me-time-%EB%AF%B8-%ED%83%80%EC%9E%84/id6711330732\" target=\"_blank\"\u003e\n      \u003cimg width=\"120\" alt=\"appstore\" src=\"https://user-images.githubusercontent.com/55099365/196023806-5eb7be0f-c7cf-4661-bb39-35a15146c33a.png\"\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n## 기획의도 (Intention)\n\u003e ### 𝑴𝒆 𝑻𝒊𝒎𝒆 : 나 혼자만의 시간, 나를 위한 휴식 시간, 나를 충전하는 시간\n\n- 하루 24시간 중 우리는 얼만큼의 시간을 '나 자신'을 위해 사용할까요?\n- 바쁜 일상에 치이며 그 속에서 작은 행복을 찾는 것도 보람있지만, '나를 잘 아는 사람'이 되는 것이 무엇보다 중요한 거 같습니다.\n- 미타임은 하루 한 번, 오롯이 자기 자신에 집중하고 알아가는 시간을 가질 수 있도록 도와줍니다.\n- 자신의 생각과 감정을 여과없이 적어내며 스스로 어떻게 살아가고 있는지 탐구하고 되돌아보며, 오롯이 자신에게 집중할 수 있는 시간을 제공합니다.\n\n\u003cbr /\u003e\n\n## 프로젝트 소개 (Description)\n\u003e **개발 기간** : 2024. 09. 12 ~ 2024. 10. 02 (약 3주)\u003cbr /\u003e\n\u003e **개발 인원** : 1명 (기획·디자인·개발)\u003cbr /\u003e\n\u003e **최소 버전** : iOS 16.0+\u003cbr /\u003e\n\u003e **지원 모드** : 세로 모드, 라이트 모드\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg width=\"19%\" src=\"https://github.com/user-attachments/assets/e0ab430f-90fd-40b9-90b7-56b8e145af6a\" /\u003e\n  \u003cimg width=\"19%\" src=\"https://github.com/user-attachments/assets/6df817b3-10b8-4d76-a7b9-004f34142e0a\" /\u003e\n  \u003cimg width=\"19%\" src=\"https://github.com/user-attachments/assets/bab41f27-3874-4015-aa8b-828bd13b2cfc\" /\u003e\n  \u003cimg width=\"19%\" src=\"https://github.com/user-attachments/assets/01c7f539-45b8-4a12-bf3a-ef7abf108620\" /\u003e\n  \u003cimg width=\"19%\" src=\"https://github.com/user-attachments/assets/7cebb207-3301-4da0-a06a-275a32911e10\" /\u003e\n\u003c/div\u003e\n\n\n\u003cbr /\u003e\u003cbr /\u003e\n\n## 사용 기술 및 개발 환경  (Tech Stack \u0026 Environment)\n- **Language \u0026 Tool** : Swift 5.1, Xcode 15.4\n- **iOS** : SwiftUI, Charts, WebKit\n- **Architecture** : MVVM\n- **Design Pattern** : Input-Output, Repository, Singleton\n- **Network** : URLSession\n- **Reactive** : Combine\n- **Local DB** : Realm\n- **Management** : Git, GitHub, Figma\n\n\u003cbr /\u003e\n\n## 아키텍쳐 (Architecture)\n\u003cimg src=\"https://github.com/user-attachments/assets/bb6112c0-d312-4bab-b394-f4fc3e6bffe5\" /\u003e\n\n- Combine과 MVVM 패턴을 활용해 UI와 Business Logic 분리 (View-ViewController 역할 분리)\n- 사용자 액션을 열거형으로 관리하고, action 메서드를 통해 Input에 새로운 값을 전달하여 Output으로 변경된 데이터 방출\n\n\u003cbr /\u003e\n\n## 개발 방식 및 브랜치 전략 (Development \u0026 Branch Strategy)\n### Issue, Pull Request(PR) 템플릿 활용한 프로젝트 관리\n- 개발 시작 전 새로운 Issue 생성 후, Issue와 브랜치를 연결하고 이슈 번호를 브랜치명에 활용하여 일관된 작업 내용 기록\n- Issue와 PR 생성 시 레이블을 표기하여 작업 종류와 진행사항을 한 눈에 알 수 있도록 처리\n- PR 생성 시 템플릿에 맞게 작업 내용과 스크린샷을 상세히 기록하여 추후에도 프로젝트 진행 현황을 알 수 있도록 문서화\n\n### 간소화된 Git Flow 도입\n- **`main`**\n  - 실제 서비스 배포용 브랜치\n  - 큰 기능 단위 개발 작업이 완료된 후 병합 (Version Realese)\n- **`dev`**\n  - 개발 및 QA 작업용 브랜치 (Main 브랜치에서 분기)\n  - 각 기능 단위 브랜치 작업이 완료된 후 병합\n- **`feat`** , **`design`**, **`fix`**, **`refactor`**...\n  - 작은 기능 단위 브랜치 (dev 브랜치에서 분기)\n  - Issue, PR, Commit 컨벤션과 동일한 Prefix 사용하여 일관된 작업 구분\n- 각 브랜치별 작업 내용 확인을 위해 브랜치명 컨벤션 도입\n  - prefix/이슈번호-작업설명\n  - `design/1-home-ui`\n\n```mermaid\n---\ntitle: Example of Me-Time Git Flow\n---\ngitGraph\n   commit id: \"initial\"\n   branch develop order: 2\n   commit id: \"develop\"\n   branch feat order: 3\n   commit id: \"feat/1-some-feature\"\n   checkout develop\n   merge feat\n   branch design order: 4\n   commit id: \"design/2-some-ui\"\n   checkout develop\n   merge design\n   branch fix order: 5\n   commit id: \"fix/3-some-error\"\n   checkout develop\n   merge fix\n   checkout main\n   merge develop tag: \"Release: v1.0.0\"\n   branch hotfix order: 1\n   commit id: \"HOTFIX/4-something-change\"\n   checkout main\n   merge hotfix tag: \"Release: v1.0.1\"\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePrefix Convention 전체보기\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv\u003e\n\n| Prefix  | Description | Prefix  | Description | \n|------------|-----------|------------|-----------|\n| Feat | 새로운 기능에 대한 커밋 | Style | UI 스타일에 관한 커밋 |\n| Fix | 버그 수정에 대한 커밋 | Refactor | 코드 리팩토링에 대한 커밋 |\n| Build | 빌드 관련 파일 수정에 대한 커밋 | Test | 테스트 코드 수정에 대한 커밋 |\n| Chore | 그 외 자잘한 수정에 대한 커밋 | Init | 프로젝트 시작에 대한 커밋 |\n| Ci | CI 관련 설정 수정에 대한 커밋 | Release | 릴리즈에 대한 커밋 |\n| Docs | 문서 수정에 대한 커밋 | WIP | 미완성 작업에 대한 임시 커밋 |           \n\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cbr /\u003e\n\n## 주요 기능 (Main Feature)\n### 모닝페이퍼 (일기)\n\u003e 작성, 열람, 댓글\n- Realm 데이터베이스를 활용한 모닝페이퍼 및 댓글 저장\n- 모닝페이퍼 작성 날짜와 열람하려는 날짜를 비교하여 열람 가능/불가능 처리\n- 1일 1작성 원칙을 기준으로 모닝페이퍼와 댓글 작성 예외 처리\n\n### 오늘의 첫 번째 감정\n\u003e 캘린더, 차트 조회, 음악 추천\n- 긍정적인 감정과 부정적인 감정을 합쳐 총 28개의 데이터 제공\n- ‘오늘의 첫 번째 감정’ 데이터를 활용하여 시각화된 통계 정보 제공\n- 사용자가 선택한 감정 기반으로 음악 플레이리스트 콘텐츠 추천\n\n\u003cbr /\u003e\n\n## 주요 기술 구현 내용 (Implementation Details)\n\n### Combine과 Input-Output 패턴을 적용한 MVVM 아키텍쳐 설계\n- Input, Output을 정의하고 transform 메서드를 통해 입출력 흐름을 관리하는 ViewModelType 프로토콜 구현\n- View에서 발생하는 사용자 액션을 열거형으로 관리하고, action 메서드를 통해 Input에 새로운 값 전달 처리\n- @Published를 활용해 Output에 변경사항 발생 시 View에 반영\n\n\u003cbr /\u003e\n\n### Realm 데이터베이스를 활용한 서비스 핵심 기능 구현\n- 앱에서 사용할 모닝페이퍼(일기)와 댓글 모델을 정의하고 List 타입을 통해 1:N 관계 설정\n- @ObservedResults를 활용해 Realm DB 변화를 관찰하여 변경사항에 따라 View 업데이트\n- @ObservedRealmObject 사용 시 수동 트랜잭션을 대응하여 모닝페이퍼 내 댓글 작성 기능 구현\n\n\u003cbr /\u003e\n\n### 날짜 관련 메서드 정의와 핸들링을 위한 DateFormatterRepository 구현\n- 일기 형태를 띄는 서비스 특성을 고려하여 날짜와 관련된 메서드를 한 곳에서 정의하여 관리\n- 포맷팅 할 문자열 케이스를 열거형으로 정의하고, Date 타입을 문자열로 포맷팅 시 해당 열거형 활용\n- 모닝페이퍼 작성 날짜와 오늘 날짜(열람 하려는 날짜)를 비교하여 열람 가능/불가능 분기 처리\n\n\u003cbr /\u003e\n\n### UI 렌더링 최적화, 공통 컴포넌트 관리\n- WrapperView를 사용해 ForEach 구문에서 데이터 변경 시 화면이 리렌더링되며 일어나는 불필요한 액션 방지\n- 앱에서 공통으로 사용하는 UI를 별도의 컴포넌트로 정의하여 코드 중복을 최소화하고, 재사용성 및 유지보수 고려 \n- iOS 17.0 이상과 이전 버전에서 사용하는 modifier를 핸들링하는 CustomModifier 구현\n\n\u003cbr /\u003e\n\n### 커스터마이징 캘린더와 Charts를 활용한 데이터 통계 시각화\n- Realm DB에 저장된 모든 데이터 가져와 선택된 날짜에 따라 필터링하여 캘린더 및 차트에 바인딩\n- 일별 데이터에 따라 캘린더 내 해당 일에 ‘오늘의 첫 번째 감정’ 이모지 표시 제공\n- 월별 데이터에 따라 BarMark를 통해  ‘오늘의 첫 번째 감정’ 데이터 통계 시각화\n- 데이터가 존재하지 않는 날짜에는 EmptyView 노출\n\n\u003cbr /\u003e\n\n### UI 차별화를 위한 커스터마이징 탭바 구현 \u0026 예외 처리\n- 탭바로 사용할 화면을 열거형으로 정의하고, ZStack을 활용해 사용자가 선택한 탭에 따라 화면 렌더링\n- 탭바 사용 유무를 다루는 환경 키(EnvironmentKey)를 추가하고, 각 화면에 주입하여 키값에 따라 탭바 숨김 처리\n\n\u003cbr /\u003e\n\n### Youtube Open API를 활용한 음악 플레이리스트 추천 기능 구현\n- Singleton 패턴으로 YoutubeAPIManager 구현하여 Query 기반 유튜브 콘텐츠 검색\n- URLSession의 dataTask 메서드를 사용해 네트워크 요청, Result Type을 통해 에러 핸들링\n\n\u003cbr /\u003e\n\u003cbr /\u003e\n\n## 트러블 슈팅  (Trouble Shooting)\n### 1. 단일 모닝페이퍼 데이터에 댓글 추가 시 트랜잭션(Transaction) 오류\n- **원인** : 상위 View에서 @ObservedResults로 가져온 전체 모닝페이퍼 리스트 중에서 @ObservedRealmObject를 통해 하위 View로 단일 데이터를 보내주었을 때, @ObservedResults의 경우 프로퍼티 래퍼 내에서 자동으로 트랜잭션을 관리해주지만 @ObservedRealmObject는 Realm 객체를 개별로 관찰하기 때문에, 해당 객체를 변경하기 위해서는 명시적으로 트랜잭션을 관리해주어야 한다.\n- **해결** : @ObservedRealmObject로 가지고 있던 단일 모닝페이퍼의 id값을 통해 Write Transaction 안에서 id가 일치하는 데이터를 찾아, 해당 데이터에 접근하여 댓글을 추가하는 방법으로 해결\n\n```swift\n// Realm 테이블 구조\n\nimport Foundation\nimport RealmSwift\n\nfinal class MorningPaper: Object, ObjectKeyIdentifiable {\n    @Persisted(primaryKey: true) var id: ObjectId\n    @Persisted(indexed: true) var title: String\n    @Persisted var content: String\n    @Persisted var createAt: Date\n    @Persisted var emotion: String\n    @Persisted var commentData: List\u003cComment\u003e\n    \n    convenience init(title: String, content: String, emotion: String) {\n        self.init()\n        self.title = title\n        self.content = content\n        self.emotion = emotion\n        self.createAt = Date()\n    }\n}\n\nfinal class Comment: Object, ObjectKeyIdentifiable {\n    @Persisted(primaryKey: true) var id: ObjectId\n    @Persisted var content: String\n    @Persisted var createAt: Date\n    \n    convenience init(content: String) {\n        self.init()\n        self.content = content\n        self.createAt = Date()\n    }\n}\n```\n\n```swift\n// 기존\n@ObservedRealmObject var detailData: MorningPaper\ndetailData.commentData.append(comment)\n\n// 수정\n@ObservedRealmObject var detailData: MorningPaper\n\ndo {\n   let realm = try Realm()\n   let data = realm.object(ofType: MorningPaper.self, forPrimaryKey: detailData.id)\n\n   guard let data = data else { ... }\n   try realm.write {\n      data.commentData.append(comment)\n   }\n} catch {\n    ...\n}\n```\n\n\u003cbr /\u003e\n\n### 2. Custom TabBar 사용 시 화면마다 TabBar Hidden 처리가 어려운 문제\n- **원인** : SwiftUI에서 TabBar Hidden 처리 시 `.toolbar(.hidden, for: .tabBar)`를 활용할 수 있지만, Custom TabBar를 구성한 경우 해당 코드로 TabBar의 Hidden 처리를 핸들링할 수 없는 문제\n- **해결** : @Environment Property Wrapper를 사용하여 각 화면마다 Custom TabBar를 Hidden 처리\n\n```swift\nimport SwiftUI\n\nprivate struct TabBarHiddenKey: EnvironmentKey {\n    static let defaultValue: Binding\u003cBool\u003e = .constant(false)\n}\n\nextension EnvironmentValues {\n    var isTabBarHidden: Binding\u003cBool\u003e {\n        get { self[TabBarHiddenKey.self] }\n        set { self[TabBarHiddenKey.self] = newValue }\n    }\n}\n```\n\n```swift\nstruct ContentView: View {\n    @State private var isTabBarHidden: Bool = false\n\n    var body: some View {\n        ZStack {\n            switch selectedTab {\n            case .main:\n                NavigationView {\n                    MorningPaperView()\n                        .environment(\\.isTabBarHidden, $isTabBarHidden)\n                }\n            .\n            .\n            . \n            }\n        }\n    }\n}\n```\n\n```swift\n// 탭바가 필요없는 뷰에서 onAppear, onDisappear 시점마다 환경값 \nstruct detailView: View {\n    @Environment(\\.isTabBarHidden) private var isTabBarHidden: Binding\u003cBool\u003e\n    var body: some View {\n        VStack { ... }\n            .onAppear { isTabBarHidden.wrappedValue = true }\n            .onDisappear { isTabBarHidden.wrappedValue = false }\n    }  \n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-junehee%2Fme-time","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdev-junehee%2Fme-time","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-junehee%2Fme-time/lists"}