Skip to content

Repository files navigation

RunningTime

RunningTime์€ ์ง‘์ค‘ ํƒ€์ด๋จธ๋ฅผ ์‹ค์ œ ์ด๋™ ๊ธฐ๋ก์ฒ˜๋Ÿผ ์‹œ๊ฐํ™”ํ•˜๋Š” iOS ์•ฑ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ์ง์ ‘ ๋งŒ๋“  ๊ฒฝ๋กœ ๋˜๋Š” MapKit ๋„๋ณด ๊ฒฝ๋กœ๋ฅผ ์„ ํƒํ•˜๊ณ , ์„ค์ •ํ•œ ์‹œ๊ฐ„ ๋™์•ˆ ํƒ€์ด๋จธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์•ฑ์€ ๊ฒฝ๋กœ ์œ„์˜ ํ˜„์žฌ ์œ„์น˜, ์†๋„, ๋‚จ์€ ์‹œ๊ฐ„, Look Around ๋˜๋Š” 3D ์ง€๋„ ํ™”๋ฉด์„ ๋ณด์—ฌ ์ฃผ๋ฉฐ, ์™„๋ฃŒ๋œ ์„ธ์…˜์€ Core Data์— ์ €์žฅ๋˜์–ด ํ™ˆ๊ณผ ์ €๋„์—์„œ ๋‹ค์‹œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์š”์•ฝ

  • UIKit ๊ธฐ๋ฐ˜ iOS ์•ฑ์ด๋ฉฐ, ์œ„์ ฏ๊ณผ Live Activity๋Š” WidgetKit/SwiftUI/ActivityKit์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ํ™”๋ฉด ์ƒํƒœ ๊ด€๋ฆฌ๋Š” ReactorKit + RxSwift ํŒจํ„ด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ํ™”๋ฉด ์ด๋™์€ AppCoordinator, WalkCoordinator, JournalCoordinator ์ค‘์‹ฌ์œผ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฝ๋กœ ์ƒ์„ฑ์€ MapKit MKDirections ๋„๋ณด ๊ฒฝ๋กœ์™€ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๊ทธ๋ฆฌ๋Š” ๋“œ๋กœ์ž‰ ๊ฒฝ๋กœ๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒฝ๋กœ ํ˜•์ƒ์€ Core Data์— ์ง์ ‘ ๋„ฃ์ง€ ์•Š๊ณ  Documents/trails/{UUID}.geojson ํŒŒ์ผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์„ธ์…˜, ์ฝ”์Šค ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ์ฆ๊ฒจ์ฐพ๊ธฐ ์ƒํƒœ๋Š” Core Data์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์œ„์ ฏ๊ณผ Live Activity ๋ณต์› ๋ฐ์ดํ„ฐ๋Š” App Group UserDefaults์™€ App Group ํŒŒ์ผ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ํƒ€์ด๋จธ ์ง„ํ–‰์€ ์‹ค์ œ GPS ์ถ”์ ์ด ์•„๋‹ˆ๋ผ, ๊ฒฝ๊ณผ ์‹œ๊ฐ„์„ ๊ฒฝ๋กœ waypoint ๋น„์œจ์— ๋งคํ•‘ํ•˜๋Š” ๊ฐ€์ƒ ์ง„ํ–‰ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๊ธฐ์ˆ  ์Šคํƒ

์˜์—ญ ์‚ฌ์šฉ ๊ธฐ์ˆ 
์–ธ์–ด Swift
๋ฉ”์ธ UI UIKit
์œ„์ ฏ/Live Activity UI SwiftUI, WidgetKit, ActivityKit
์ƒํƒœ ๊ด€๋ฆฌ ReactorKit 3.2.0, RxSwift 6.10.2, RxCocoa
๋ ˆ์ด์•„์›ƒ SnapKit 5.7.1
์ง€๋„/๊ฒฝ๋กœ MapKit, CoreLocation, MKDirections, MKLookAroundSceneRequest
์ €์žฅ์†Œ Core Data, FileManager, App Group UserDefaults
์Šคํฌ๋ฆฐํƒ€์ž„ FamilyControls, ManagedSettings
ํ”„๋กœ์ ํŠธ Xcode project, Swift Package Manager

xcodebuild -list -project RunningTime.xcodeproj ๊ธฐ์ค€์œผ๋กœ RunningTime, RunningTimeTests, RunningTimeUITests, FavoriteCourseWidgetExtension ํƒ€๊นƒ๊ณผ RunningTime, FavoriteCourseWidgetExtension ์Šคํ‚ด์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์•ฑ ๊ตฌ์กฐ

RunningTime
โ”œโ”€โ”€ App
โ”‚   โ”œโ”€โ”€ AppCoordinator
โ”‚   โ”œโ”€โ”€ AppDIContainer
โ”‚   โ”œโ”€โ”€ CustomTabBarController
โ”‚   โ””โ”€โ”€ Coordinators
โ”œโ”€โ”€ DesignSystem
โ”‚   โ”œโ”€โ”€ Colors / Typography / Spacing / Animation
โ”‚   โ””โ”€โ”€ Components
โ”œโ”€โ”€ Features
โ”‚   โ”œโ”€โ”€ Home
โ”‚   โ”œโ”€โ”€ RouteBuilder
โ”‚   โ”œโ”€โ”€ PlaceSearch
โ”‚   โ”œโ”€โ”€ CourseDetail
โ”‚   โ”œโ”€โ”€ TimerSetting
โ”‚   โ”œโ”€โ”€ WalkTimer
โ”‚   โ”œโ”€โ”€ SessionReport
โ”‚   โ””โ”€โ”€ Journal
โ”œโ”€โ”€ Shared
โ”‚   โ”œโ”€โ”€ Services
โ”‚   โ”œโ”€โ”€ db
โ”‚   โ”œโ”€โ”€ AppGroup
โ”‚   โ”œโ”€โ”€ Models
โ”‚   โ””โ”€โ”€ Utils
FavoriteCourseWidget
โ””โ”€โ”€ Widget / Control Widget / Live Activity

์•„ํ‚คํ…์ฒ˜

Coordinator

์•ฑ ๋ฃจํŠธ๋Š” AppCoordinator๊ฐ€ ์†Œ์œ ํ•ฉ๋‹ˆ๋‹ค.

Walk ํƒญ์€ WalkCoordinator๊ฐ€ Home, RouteBuilder, CourseDetail, WalkTimer, SessionReport, PlaceSearch ์ด๋™์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. Journal ํƒญ์€ JournalCoordinator๊ฐ€ Journal ๋ชฉ๋ก๊ณผ ์„ธ์…˜ ์ƒ์„ธ ํ™”๋ฉด ์ด๋™์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ง€์›ํ•˜๋Š” ๋”ฅ๋งํฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • runningtime://home
  • runningtime://course?id={UUID}
  • runningtime://resume-timer

DI Container

AppDIContainer๋Š” ํ™”๋ฉด๊ณผ Reactor ์กฐํ•ฉ์„ ์ƒ์„ฑํ•˜๊ณ  ์•ฑ ์ˆ˜๋ช… ๋™์•ˆ ๊ณต์œ ๋˜๋Š” ์„œ๋น„์Šค๋ฅผ ๋ณด๊ด€ํ•ฉ๋‹ˆ๋‹ค.

  • PersistenceController.shared
  • LookAroundService
  • LookAroundPlaceService
  • SessionSaveService

WalkTimerReactor๋Š” LookAroundServiceProtocol, LookAroundPlaceServiceProtocol, SessionSaveServiceProtocol, ScreenTimeServiceProtocol์„ ์ƒ์„ฑ์ž ์ฃผ์ž…์œผ๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค.

ReactorKit / RxSwift

๊ฐ ์ฃผ์š” ํ™”๋ฉด์€ Reactor์˜ Action -> Mutation -> State ํ๋ฆ„์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ํ™”๋ฉด ์ด๋™์ฒ˜๋Ÿผ ํ•œ ๋ฒˆ๋งŒ ์†Œ๋น„๋˜์–ด์•ผ ํ•˜๋Š” ์ด๋ฒคํŠธ๋Š” ReactorKit์˜ @Pulse๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ:

  • Home: ๊ฒ€์ƒ‰์–ด, ์ €์žฅ๋œ ์ฝ”์Šค ๋ชฉ๋ก, RouteBuilder/CourseDetail ์ด๋™ ์‹ ํ˜ธ
  • RouteBuilder: ์ถœ๋ฐœ์ง€/๋„์ฐฉ์ง€, ๋กœ๋”ฉ, MapKit ๊ฒฝ๋กœ, ๋“œ๋กœ์ž‰ ๊ฒฝ๋กœ, ์ €์žฅ ์ƒํƒœ, WalkTimer ์ด๋™ ์‹ ํ˜ธ
  • WalkTimer: ํƒ€์ด๋จธ ๊ฒฝ๊ณผ ์‹œ๊ฐ„, ์ผ์‹œ์ •์ง€, ๊ฒฝ๋กœ ์ง„ํ–‰๋ฅ , Look Around ์ƒํƒœ, Live Activity ์ƒํƒœ, ์ €์žฅ ์ƒํƒœ
  • Journal: ์„ ํƒ ๋‚ ์งœ, ์ฃผ/์›” ๋ฒ”์œ„, ์„ธ์…˜ ๋ชฉ๋ก, ํ†ต๊ณ„

์ฃผ์š” ๊ธฐ๋Šฅ

1. Home

Home์€ Core Data์— ์ €์žฅ๋œ Route๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์ฆ๊ฒจ์ฐพ๊ธฐ ์ฝ”์Šค์™€ ์ผ๋ฐ˜ ์ฝ”์Šค๋กœ ๋ถ„๋ฆฌํ•ด ๋ณด์—ฌ ์ค๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • isFavorite == true ์ฝ”์Šค์™€ isFavorite == false ์ฝ”์Šค๋ฅผ ๋ณ„๋„ ์„น์…˜์œผ๋กœ ํ‘œ์‹œ
  • ์ฆ๊ฒจ์ฐพ๊ธฐ๋Š” ์ด๋ฆ„์ˆœ, ์ผ๋ฐ˜ ์ฝ”์Šค๋Š” ์ƒ์„ฑ์ผ ์—ญ์ˆœ ์ •๋ ฌ
  • ๊ฒ€์ƒ‰์–ด๋กœ ์ œ๋ชฉ, ๋„์‹œ, ์ถœ๋ฐœ์ง€, ๋„์ฐฉ์ง€ ํ•„ํ„ฐ๋ง
  • ์ฝ”์Šค ์„ ํƒ ์‹œ CourseDetail๋กœ ์ด๋™
  • ๊ฒฝ๋กœ ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ์„ ํ†ตํ•ด RouteBuilder๋กœ ์ด๋™
  • Home ์ง„์ž… ํ›„ RouteBuilder๋ฅผ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด MapKit ์ดˆ๊ธฐํ™” ๋น„์šฉ์„ ์•ž๋‹น๊ธฐ๋Š” ํ”„๋ฆฌ๋กœ๋“œ ๋กœ์ง

2. Place Search

Place Search๋Š” MapKit ๊ฒ€์ƒ‰ API๋ฅผ ์‚ฌ์šฉํ•ด ์žฅ์†Œ๋ฅผ ์„ ํƒํ•˜๋Š” ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • MKLocalSearchCompleter ๊ธฐ๋ฐ˜ ์ž๋™์™„์„ฑ
  • ์ž๋™์™„์„ฑ ์„ ํƒ ์‹œ MKLocalSearch๋กœ ์ขŒํ‘œ์™€ ์ฃผ์†Œ ์ •๋ณด ํ™•์ •
  • PlaceItem์„ ์ฝœ๋ฐฑ์œผ๋กœ RouteBuilder์— ์ „๋‹ฌ
  • ๋’ค๋กœ๊ฐ€๊ธฐ ์•ก์…˜

3. Route Builder

RouteBuilder๋Š” ํƒ€์ด๋จธ์— ์‚ฌ์šฉํ•  ๊ฒฝ๋กœ๋ฅผ ๋งŒ๋“œ๋Š” ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • ์ถœ๋ฐœ์ง€์™€ ๋„์ฐฉ์ง€ ์„ ํƒ
  • MKDirections.Request.transportType = .walking ๊ธฐ๋ฐ˜ ๋„๋ณด ๊ฒฝ๋กœ ๊ณ„์‚ฐ
  • ์ถœ๋ฐœ/๋„์ฐฉ annotation ๋“œ๋ž˜๊ทธ ํ›„ reverse geocoding ๋ฐ ๊ฒฝ๋กœ ์žฌ๊ณ„์‚ฐ
  • ์‚ฌ์šฉ์ž๊ฐ€ ์ง€๋„ ์œ„์— ์ง์ ‘ ๊ฒฝ๋กœ๋ฅผ ๊ทธ๋ฆฌ๋Š” Drawing Mode
  • ์ง์ ‘ ๊ทธ๋ฆฐ ๊ฒฝ๋กœ์˜ ์ด ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
  • ๊ทธ๋ฆฐ ๊ฒฝ๋กœ๋ฅผ ํ™•์ •ํ•˜๋ฉด ๋„์ฐฉ ์ขŒํ‘œ๋ฅผ reverse geocoding
  • ๊ฒฝ๋กœ ๊ฒฐ๊ณผ์—์„œ ํƒ€์ด๋จธ ์„ค์ • ํ™”๋ฉด์œผ๋กœ ์ง„์ž…
  • tapWalk ์‹œ ํ˜„์žฌ ๊ฒฝ๋กœ๋ฅผ Course ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด WalkTimer๋กœ ์ „๋‹ฌ

4. Timer Setting

TimerSetting ํ™”๋ฉด์€ ์„ ํƒํ•œ ๊ฒฝ๋กœ๋ฅผ ์–ด๋–ค ์‹œ๊ฐ„์œผ๋กœ ์ง„ํ–‰ํ• ์ง€ ์ •ํ•˜๋Š” ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • ์ฝ”์Šค ๊ฑฐ๋ฆฌ์™€ ์˜ˆ์ƒ ์‹œ๊ฐ„ ํ‘œ์‹œ
  • ์‚ฌ์šฉ์ž๊ฐ€ ์ปค์Šคํ…€ ํƒ€์ด๋จธ ์‹œ๊ฐ„์„ ์„ ํƒ
  • ์„ ํƒํ•œ ์‹œ๊ฐ„์„ customTimerMinutes๋กœ WalkTimer์— ์ „๋‹ฌ

5. Course Detail

CourseDetail์€ ์ €์žฅ๋œ ์ฝ”์Šค์˜ ์ƒ์„ธ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • ์ฝ”์Šค ์ œ๋ชฉ, ์ถœ๋ฐœ์ง€, ๋„์ฐฉ์ง€, ๊ฑฐ๋ฆฌ, ์˜ˆ์ƒ ์‹œ๊ฐ„ ํ‘œ์‹œ
  • ์ €์žฅ๋œ ์ธ๋„ค์ผ์ด ์žˆ์œผ๋ฉด ์ธ๋„ค์ผ ์‚ฌ์šฉ
  • ์ธ๋„ค์ผ์ด ์—†์œผ๋ฉด MapKit ๊ฒฝ๋กœ polyline์œผ๋กœ ์ง€๋„ ํ‘œ์‹œ
  • ์ฆ๊ฒจ์ฐพ๊ธฐ ํ† ๊ธ€
  • ์ฆ๊ฒจ์ฐพ๊ธฐ ์„ค์ • ์‹œ ์œ„์ ฏ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ 
  • ์ฝ”์Šค ์‚ญ์ œ
  • ์‚ญ์ œ ์‹œ Core Data Route ์‚ญ์ œ ๋ฐ GeoJSON ํŒŒ์ผ ์‚ญ์ œ
  • ํƒ€์ด๋จธ ์„ค์ • ํ™”๋ฉด์œผ๋กœ ์ง„์ž…

6. Walk Timer

WalkTimer๋Š” ์ด ํ”„๋กœ์ ํŠธ์˜ ํ•ต์‹ฌ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์œ„์น˜๋ฅผ ์ถ”์ ํ•˜๋Š” ์šด๋™ ๊ธฐ๋ก์ด ์•„๋‹ˆ๋ผ, ์ •ํ•ด์ง„ ํƒ€์ด๋จธ ์‹œ๊ฐ„ ๋™์•ˆ ์„ ํƒํ•œ ๊ฒฝ๋กœ ์œ„๋ฅผ ๊ฐ€์ƒ์œผ๋กœ ์ด๋™ํ•˜๋Š” ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • DispatchSourceTimer๋ฅผ 0.1์ดˆ ์ฃผ๊ธฐ๋กœ ์‹คํ–‰
  • ์‹œ์ž‘ ์‹œ๊ฐ Date๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ฒฝ๊ณผ ์‹œ๊ฐ„์„ ๊ณ„์‚ฐ
  • ๊ฒฝ๊ณผ ์‹œ๊ฐ„ / ์ „์ฒด ์‹œ๊ฐ„ ๋น„์œจ์„ ๊ฒฝ๋กœ waypoint ์ง„ํ–‰๋ฅ ๋กœ ๋ณ€ํ™˜
  • ํ˜„์žฌ ์ขŒํ‘œ, waypoint index, ์ง„ํ–‰๋ฅ , bearing ๊ณ„์‚ฐ
  • 1์ดˆ ๋‹จ์œ„ Live Activity ๊ฐฑ์‹ 
  • 10์ดˆ ๋‹จ์œ„ Look Around ๋˜๋Š” ์žฅ์†Œ ์ •๋ณด ๊ฐฑ์‹ 
  • Look Around ์ง€์› ์—ฌ๋ถ€ ํ™•์ธ
  • Look Around ๋ฏธ์ง€์› ๋˜๋Š” ์‹คํŒจ ์‹œ 3D MapKit ์นด๋ฉ”๋ผ ๋ชจ๋“œ ์‚ฌ์šฉ
  • ์ผ์‹œ์ •์ง€, ์žฌ๊ฐœ, ์™„๋ฃŒ, ์ทจ์†Œ
  • ํƒ€์ด๋จธ ์‹œ์ž‘ ์‹œ ํ™œ์„ฑ ์„ธ์…˜ ์Šค๋ƒ…์ƒท ์ €์žฅ
  • ์•ฑ ๊ฐ•์ œ ์ข…๋ฃŒ ํ›„ Live Activity๊ฐ€ ์‚ด์•„ ์žˆ์œผ๋ฉด runningtime://resume-timer๋กœ ์„ธ์…˜ ๋ณต์›
  • ์„ ํƒ์ ์œผ๋กœ Screen Time ์ฐจ๋‹จ ๊ธฐ๋Šฅ ์‹คํ–‰
  • ์™„๋ฃŒ ์‹œ SessionSaveService๋ฅผ ํ†ตํ•ด ์„ธ์…˜ ์ €์žฅ

ํƒ€์ด๋จธ ๋ณต์› ๋ฐฉ์‹:

  • ์ง„ํ–‰ ์ค‘ ์„ธ์…˜์€ App Group UserDefaults์˜ activeSession์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
  • ์ €์žฅ ๋‚ด์šฉ์—๋Š” ์ฝ”์Šค ID, ์ด๋ฆ„, ์ถœ๋ฐœ/๋„์ฐฉ์ง€, ๊ฑฐ๋ฆฌ, ์ด ์‹œ๊ฐ„, waypoint lat/lng ๋ฐฐ์—ด, ์ค‘์‹ฌ ์ขŒํ‘œ, ์‹œ์ž‘ ์‹œ๊ฐ, ์ผ์‹œ์ •์ง€ ์—ฌ๋ถ€, ์ง„์ž… ์ถœ์ฒ˜, ์ธ๋„ค์ผ ๊ฒฝ๋กœ, ์ฆ๊ฒจ์ฐพ๊ธฐ ์—ฌ๋ถ€, Look Around ์ง€์› ์—ฌ๋ถ€, Screen Time ํ™œ์„ฑ ์—ฌ๋ถ€๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
  • courseDetail์—์„œ ์‹œ์ž‘ํ•œ ์„ธ์…˜์€ Core Data Route๋ฅผ ๋‹ค์‹œ ์กฐํšŒํ•ด ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค.
  • routeBuilder์—์„œ ์‹œ์ž‘ํ•œ ์„ธ์…˜์€ ์•„์ง DB์— ์—†์„ ์ˆ˜ ์žˆ์–ด snapshot waypoint๋กœ Course๋ฅผ ์žฌ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • snapshot์€ waypoint๋ฅผ ์ตœ๋Œ€ 500๊ฐœ๋กœ ์ค„์—ฌ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

7. Session Report

SessionReport๋Š” ํƒ€์ด๋จธ ์™„๋ฃŒ ํ›„ ๋ณด์—ฌ ์ฃผ๋Š” ์š”์•ฝ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • ์ฝ”์Šค๋ช…, ์ถœ๋ฐœ์ง€, ๋„์ฐฉ์ง€, ๋‚ ์งœ ํ‘œ์‹œ
  • ํƒ€์ด๋จธ ์‹œ๊ฐ„, ๊ฑฐ๋ฆฌ, ํŽ˜์ด์Šค ํ‘œ์‹œ
  • ๊ฒฝ๋กœ ์‹œ๊ฐํ™”
  • ๋‹ซ๊ธฐ ์•ก์…˜์„ ํ†ตํ•ด ํ™ˆ์œผ๋กœ ์ด๋™

SessionSummaryViewController๋ฅผ ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค๋กœ ๋‘๊ณ  JournalDetail๊ณผ ๊ณตํ†ต UI๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

8. Journal

Journal์€ ์™„๋ฃŒ๋œ ์„ธ์…˜์„ ์บ˜๋ฆฐ๋”์™€ ๋ชฉ๋ก์œผ๋กœ ํ™•์ธํ•˜๋Š” ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • ์ฃผ๊ฐ„/์›”๊ฐ„ ์บ˜๋ฆฐ๋” scope ์ „ํ™˜
  • ์ด์ „/๋‹ค์Œ ๋‹ฌ ์ด๋™
  • ์„ธ์…˜์ด ์žˆ๋Š” ๋‚ ์งœ dot ํ‘œ์‹œ
  • ์„ ํƒํ•œ ๋‚ ์งœ์˜ ์„ธ์…˜๋งŒ ํ•„ํ„ฐ๋ง
  • ์„ ํƒ ๋ฒ”์œ„์˜ ์ด ์ง‘์ค‘ ์‹œ๊ฐ„, ์„ธ์…˜ ์ˆ˜, ์ด ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
  • ์„ธ์…˜ ํ–‰ ์„ ํƒ ์‹œ ์ƒ์„ธ ํ™”๋ฉด ์ด๋™
  • JournalDetail์—์„œ ๊ณผ๊ฑฐ ์„ธ์…˜ ์š”์•ฝ๊ณผ ์ธ๋„ค์ผ ํ‘œ์‹œ

ํ†ต๊ณ„ ๊ณ„์‚ฐ ๋ฐฉ์‹:

  • ์ง‘์ค‘ ์‹œ๊ฐ„: ์„ธ์…˜ elapsedSeconds ํ•ฉ๊ณ„
  • ์„ธ์…˜ ์ˆ˜: ํ•„ํ„ฐ๋ง๋œ SessionEntity ๊ฐœ์ˆ˜
  • ๊ฑฐ๋ฆฌ: ์—ฐ๊ฒฐ๋œ route.distanceMeters ํ•ฉ๊ณ„

9. Widget

FavoriteCourseWidgetExtension์€ ์„ธ ๊ฐ€์ง€ WidgetBundle ํ•ญ๋ชฉ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

  • FavoriteCourseWidget
  • FavoriteCourseWidgetControl
  • FavoriteCourseWidgetLiveActivity

FavoriteCourseWidget:

  • App Group UserDefaults์—์„œ ์ฆ๊ฒจ์ฐพ๊ธฐ ์ฝ”์Šค ๋˜๋Š” ์ตœ๊ทผ ์ €์žฅ ์ฝ”์Šค๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค.
  • ์šฐ์„ ์ˆœ์œ„๋Š” favorite course, ๊ทธ๋‹ค์Œ saved course์ž…๋‹ˆ๋‹ค.
  • timeline refresh policy๋Š” 1์‹œ๊ฐ„ ํ›„ ๊ฐฑ์‹ ์ž…๋‹ˆ๋‹ค.
  • ์†Œํ˜•/์ค‘ํ˜• ์œ„์ ฏ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์ธ๋„ค์ผ ๋ฐฐ๊ฒฝ, ๊ทธ๋ผ๋””์–ธํŠธ, ์ œ๋ชฉ, ๊ฑฐ๋ฆฌ, ์ถœ๋ฐœ/๋„์ฐฉ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”์Šค๊ฐ€ ์—†์œผ๋ฉด EmptyWidgetContentView๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
  • ์œ„์ ฏ ํƒญ ์‹œ ์ฝ”์Šค๊ฐ€ ์žˆ์œผ๋ฉด runningtime://course?id={UUID}, ์—†์œผ๋ฉด runningtime://home์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  • Scene ์—ฐ๊ฒฐ ์‹œ App Group UserDefaults๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด Core Data์—์„œ ์ฆ๊ฒจ์ฐพ๊ธฐ/์ตœ๊ทผ ์ €์žฅ ์ฝ”์Šค๋ฅผ ๋™๊ธฐํ™”ํ•˜๊ณ  Widget timeline์„ reloadํ•ฉ๋‹ˆ๋‹ค.

Live Activity:

  • Lock Screen/Banner์™€ Dynamic Island compact/minimal/expanded UI๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • ์‹œ์ž‘ ์œ„์น˜, ๋„์ฐฉ ์œ„์น˜, ๋„์‹œ, ๋‚จ์€ ์‹œ๊ฐ„, ์ผ์‹œ์ •์ง€ ์‹œ๊ฐ„, ์™„๋ฃŒ ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
  • ํƒญ URL์€ runningtime://resume-timer์ž…๋‹ˆ๋‹ค.

Control Widget:

  • FavoriteCourseWidgetControl ํŒŒ์ผ์€ ์กด์žฌํ•˜์ง€๋งŒ ํ˜„์žฌ currentValue()๊ฐ€ ํ•ญ์ƒ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  StartTimerIntent.perform()๋„ ์‹ค์ œ ํƒ€์ด๋จธ ์‹œ์ž‘/์ •์ง€ ๋กœ์ง ์—†์ด .result()๋งŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ ํ˜„์žฌ README์—์„œ๋Š” ์™„์„ฑ ๊ธฐ๋Šฅ์ด ์•„๋‹ˆ๋ผ placeholder ์„ฑ๊ฒฉ์˜ ์ฝ”๋“œ๋กœ ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ๋ชจ๋ธ

Core Data ๋ชจ๋ธ์€ RouteEntity์™€ SessionEntity ๋‘ ๊ฐœ์ž…๋‹ˆ๋‹ค.

RouteEntity

ํ•„๋“œ ์„ค๋ช…
id Route UUID
name ์ฝ”์Šค ์ด๋ฆ„
originName ์ถœ๋ฐœ์ง€ ์ด๋ฆ„
destinationName ๋„์ฐฉ์ง€ ์ด๋ฆ„
distanceMeters ๊ฒฝ๋กœ ๊ฑฐ๋ฆฌ
geoJSONPath Documents/trails/... ํ˜•์‹์˜ GeoJSON ์ƒ๋Œ€ ๊ฒฝ๋กœ
thumbnailPath Documents ๊ธฐ์ค€ ์ธ๋„ค์ผ ์ƒ๋Œ€ ๊ฒฝ๋กœ
isFavorite ์ฆ๊ฒจ์ฐพ๊ธฐ ์—ฌ๋ถ€
completedSessionCount ์™„๋ฃŒ ์„ธ์…˜ ์ˆ˜
createdAt ์ƒ์„ฑ์ผ
lastSessionDate ๋งˆ์ง€๋ง‰ ์„ธ์…˜ ๋‚ ์งœ
sessions ์—ฐ๊ฒฐ๋œ SessionEntity ๋ชฉ๋ก

SessionEntity

ํ•„๋“œ ์„ค๋ช…
id Session UUID
targetDurationSeconds ๋ชฉํ‘œ ์‹œ๊ฐ„
elapsedSeconds ์‹ค์ œ ๊ฒฝ๊ณผ ์‹œ๊ฐ„
isCompleted ์™„๋ฃŒ ์—ฌ๋ถ€
createdAt ์ƒ์„ฑ์ผ
route ์—ฐ๊ฒฐ๋œ RouteEntity

Route ์‚ญ์ œ ์‹œ ์—ฐ๊ฒฐ๋œ Session์€ cascade ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. Session์—์„œ Route๋กœ์˜ ๊ด€๊ณ„ ์‚ญ์ œ ๊ทœ์น™์€ nullify์ž…๋‹ˆ๋‹ค.

ํŒŒ์ผ ์ €์žฅ ๋ฐฉ์‹

GeoJSON

๊ฒฝ๋กœ ์ขŒํ‘œ๋Š” Core Data๊ฐ€ ์•„๋‹ˆ๋ผ GeoJSON ํŒŒ์ผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

์ €์žฅ ์œ„์น˜:

Documents/trails/{UUID}.geojson

GeoJSON ์ขŒํ‘œ ์ˆœ์„œ๋Š” ํ‘œ์ค€์— ๋งž์ถฐ [longitude, latitude]์ž…๋‹ˆ๋‹ค. ์ฝ์„ ๋•Œ๋Š” ๋‹ค์‹œ CLLocationCoordinate2D(latitude:longitude:)๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

RouteGeoJSONEncoder.loadCoordinates(from:)๋Š” Documents/ prefix๊ฐ€ ์žˆ๋Š” ๊ฒฝ๋กœ๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ƒ๋Œ€ ๊ฒฝ๋กœ ํ˜•์‹์€ ๋นˆ waypoint ๋ฐฐ์—ด๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

Thumbnail

์„ธ์…˜ ์™„๋ฃŒ ์‹œ ์ธ๋„ค์ผ์ด ์žˆ์œผ๋ฉด ๋‹ค์Œ ์œ„์น˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

Documents/thumbnails/{routeID}.jpg

์œ„์ ฏ ํ‘œ์‹œ์šฉ ์ธ๋„ค์ผ์€ App Group ์ปจํ…Œ์ด๋„ˆ์˜ widget-thumbnails ๋””๋ ‰ํ„ฐ๋ฆฌ์— 600px ์ดํ•˜ JPEG๋กœ ๋ณต์‚ฌ๋ฉ๋‹ˆ๋‹ค.

App Group ๋ฐ์ดํ„ฐ ๊ณ„์•ฝ

App Group ID:

group.com.sunkeydokey.RunningTime

UserDefaults keys:

Key ๋‚ด์šฉ
widget.favoriteCourse ์œ„์ ฏ์— ํ‘œ์‹œํ•  ์ฆ๊ฒจ์ฐพ๊ธฐ ์ฝ”์Šค
widget.savedCourse ์ตœ๊ทผ ์ €์žฅ ์ฝ”์Šค
activeSession ์ง„ํ–‰ ์ค‘์ธ ํƒ€์ด๋จธ ๋ณต์› snapshot

์œ„์ ฏ ์ฝ”์Šค ์—”ํŠธ๋ฆฌ๋Š” ๋‹ค์Œ ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

  • id
  • title
  • city
  • durationMinutes
  • distanceMeters
  • origin
  • destination
  • sharedThumbnailPath

decoder๋Š” ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ์™€์˜ ํ˜ธํ™˜์„ ์œ„ํ•ด distanceMeters, origin, destination, sharedThumbnailPath ๋ˆ„๋ฝ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

Screen Time ๊ธฐ๋Šฅ

DefaultScreenTimeService๋Š” FamilyControls์™€ ManagedSettings๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„๋œ ๋™์ž‘:

  • AuthorizationCenter.shared.requestAuthorization(for: .individual)๋กœ ๊ถŒํ•œ ์š”์ฒญ
  • ์Šน์ธ ์‹œ ManagedSettingsStore๋กœ ์ „์ฒด ์•ฑ ์นดํ…Œ๊ณ ๋ฆฌ, ์›น ๋„๋ฉ”์ธ ์นดํ…Œ๊ณ ๋ฆฌ, ์•ฑ ์„ค์น˜๋ฅผ ์ฐจ๋‹จ
  • ํƒ€์ด๋จธ ์ข…๋ฃŒ ๋˜๋Š” ํ•ด์ œ ์‹œ clearAllSettings()
  • ๊ถŒํ•œ ์‹คํŒจ ์‹œ ํƒ€์ด๋จธ ์ž์ฒด๋Š” ๊ณ„์† ์ง„ํ–‰

ํ˜„์žฌ ๊ตฌํ˜„์€ ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ์•ฑ์„ ๊ณ ๋ฅด๋Š” ๋ฐฉ์‹์ด ์•„๋‹ˆ๋ผ device-wide shield์— ๊ฐ€๊น์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ

ํ˜„์žฌ RunningTimeTests์—๋Š” GeoManagerTests๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒ€์ฆ ๋ฒ”์œ„:

  • GeoJSON top-level ๊ตฌ์กฐ
  • GeoJSON ์ขŒํ‘œ ์ˆœ์„œ๊ฐ€ [longitude, latitude]์ธ์ง€ ํ™•์ธ
  • 20m ๊ธฐ๋ณธ ๋ณด๊ฐ„ ๊ฐ„๊ฒฉ
  • ์งง์€ segment๋Š” ๋ณด๊ฐ„ํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ
  • ๋ณด๊ฐ„ ๊ฒฐ๊ณผ๊ฐ€ ์‹œ์ž‘/๋ ์ขŒํ‘œ๋ฅผ ๋ณด์กดํ•˜๋Š”์ง€ ํ™•์ธ
  • ๋ณด๊ฐ„ ์ขŒํ‘œ๊ฐ€ segment bounds ์•ˆ์— ์žˆ๋Š”์ง€ ํ™•์ธ
  • ๋นˆ ๋ฐฐ์—ด, ๋‹จ์ผ ์ขŒํ‘œ edge case
  • bearing north/south/east/west ๋ฐ 0..<360 ์ •๊ทœํ™”
  • GeoJSON ํŒŒ์ผ ์ €์žฅ ๋ฐ JSON ํŒŒ์‹ฑ
  • CLGeocoder reverse geocoding ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ
  • MKDirections walking route ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ
  • ๊ฐ™์€ ์ขŒํ‘œ ๊ฒฝ๋กœ ์š”์ฒญ edge case

ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์˜ˆ์‹œ:

xcodebuild test -project RunningTime.xcodeproj -scheme RunningTime -destination 'platform=iOS Simulator,name=iPhone 16'

์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ด๋ฆ„์€ ๋กœ์ปฌ Xcode ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ๋ฐ”๊ฟ”์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋นŒ๋“œ ๋ฐ ์‹คํ–‰

์š”๊ตฌ ์กฐ๊ฑด:

  • Xcode
  • iOS 18 ์ด์ƒ ์•ฑ/์œ„์ ฏ ํƒ€๊นƒ
  • App Group capability
  • Family Controls entitlement
  • WidgetKit/ActivityKit ์ง€์› ํ™˜๊ฒฝ

ํ”„๋กœ์ ํŠธ ์ •๋ณด ํ™•์ธ:

xcodebuild -list -project RunningTime.xcodeproj

๋นŒ๋“œ ์˜ˆ์‹œ:

xcodebuild build -project RunningTime.xcodeproj -scheme RunningTime -destination 'generic/platform=iOS Simulator'

์•ฑ ํƒ€๊นƒ๊ณผ ์œ„์ ฏ ํƒ€๊นƒ์˜ deployment target์€ ํ”„๋กœ์ ํŠธ ํŒŒ์ผ ๊ธฐ์ค€ iOS 18.0์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ํƒ€๊นƒ์—๋Š” iOS 26.2 deployment target ์„ค์ •์ด ๋“ค์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„์ ฏ ์ „์šฉ ์Šคํ‚ด FavoriteCourseWidgetExtension์€ SpringBoard๋ฅผ ํ†ตํ•ด ์œ„์ ฏ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•˜๋ฉฐ _XCWidgetFamily=systemMedium, _XCWidgetKind=FavoriteCourseWidget ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages