λ³μ μΈμ λ μ°λ¦¬λ₯Ό λ³Έλ€. μ°λ¦¬κ° λ³Ό μ μμ λΏ.
λΉμ μ μμΌλ³(Birth Star)μ΄ μ§κΈ μ΄ μκ° μ§κ΅¬ μ΄λλ₯Ό λΉμΆκ³ μμκΉμ?
μ€μ¬μ κ·Έ μ¬μ μ μ€μκ°μΌλ‘ μΆμ νκ³ , κ°μ μ λ΄μ κΈ°λ‘νλ μ²μ²΄ μ¬ν λ€μ΄μ΄λ¦¬μ
λλ€.

- MapKit κΈ°λ° μ€μκ° μ±νμ (Sub-stellar Point) μκ°ν
- μμΌ κΈ°λ° μνΈμ±μ κ³ λΒ·λ°©μκ° κ³μ°
- WidgetμΌλ‘ μ€λμ λ³ μμΉ νλμ νμΈ
- μ€νλΌμΈ μ§μ: μΈν°λ· μμ΄λ μ²μ²΄ κ³μ° κ°λ₯
- λ³μ΄ μ§λκ° νΉλ³ν μ₯μλ€μ Core Data κΈ°λ°μΌλ‘ κΈ°λ‘
- μκ°μ μ’ν, μκ°, 무λλ₯Ό μ€ν¬νμ²λΌ μμ§
- λλ§μ μ²μ²΄ κ΄μΈ‘ μμΉ΄μ΄λΈ ꡬμ±
- μ¬μ§ + μ§§μ μΌκΈ°λ‘ μΆμ΅ 보κ΄
- κ΄μ¬ μ§μ(POI) λ±λ‘ μ λ³μ΄ ν΅κ³Όνλ μκ° μλ¦Ό
- λ°±κ·ΈλΌμ΄λ νμ΄λ¨Έμ μμΉ μΌμλ‘ μ νν κ°μ§
- μκ°λ μλ λ³ν(UTC β Local Time)
| Layer | Framework | Why? |
|---|---|---|
| UI Framework | SwiftUI + UIKit | λ©μΈμ SwiftUI, μμΉ΄μ΄λΈ λͺ¨λμ UIKit (ReactorKit μ§μ) |
| State Management | TCA (The Composable Architecture) | Single Source of Truth ν¨ν΄ |
| Reactive | RxSwift | μΌμ μ€νΈλ¦Ό & λ°±κ·ΈλΌμ΄λ νμ΄λ¨Έ μ μ²λ¦¬ |
| Data Persistence | Core Data | μ€νλΌμΈ μΊμ± + κ΄μΈ‘ κΈ°λ‘ μ μ₯ |
| Physics Engine | SwiftAA | Jean Meeus μκ³ λ¦¬μ¦ κΈ°λ° μ λ° μ²μ²΄ μ’ν κ³μ° |
| Mapping | MapKit | μ±νμ μΈκ³ μ§λ λ λλ§ |
| Architecture (Sub) | ReactorKit | μμΉ΄μ΄λΈ λͺ¨λ λ 립 κ΅¬μ± |
βββββββββββββββββββββββββββββββββββ
β User Input (Birthday + Location) β
ββββββββββββββ¬βββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββ
β RxSwift Stream β
β Timer + Location β
β Sensor Processing β
ββββββββββ¬ββββββββββββ
β
βΌ
ββββββββββββββββββββββ
β TCA Reducer β
β Action Handler β
ββββββββββ¬ββββββββββββ
β
βΌ
ββββββββββββββββββββββ
β SwiftAA Engine β
β (Jean Meeus Algo) β
β β β β β
β RA, Dec, Alt, Az β
ββββββββββ¬ββββββββββββ
β
βββββββββ΄βββββββββββ
β β
βΌ βΌ
ββββββββββββββββ ββββββββββββββββ
β SwiftUI View β β Core Data β
β MapKit β β Persistence β
β Render β β (Manual Save)β
ββββββββββββββββ ββββββββββββββββ
// μμΌλ³ μ 보
@Model
final class BirthStar {
var birthDate: Date
var constellation: String?
var cachedEphemeris: [String: Double]? // κ³μ° κ²°κ³Ό μΊμ
var observations: [ObservationRecord] = []
}
// κ΄μΈ‘ κΈ°λ‘ (μ€ν¬ν)
@Model
final class ObservationRecord {
var timestamp: Date
var latitude: Double
var longitude: Double
var altitude: Double
var azimuth: Double
var mood: String?
var diary: String?
var imageData: Data?
// μμ°Έμ‘°
var birthStar: BirthStar?
}
// νΉλ³ν μκ° (μ¬μ§ + λ©νλ°μ΄ν°)
@Model
final class CapturedMoment {
var recordedAt: Date
var imageData: Data
var location: String?
var physicsMetadata: String? // JSON: {ra, dec, mag, etc}
}βββββββββββββββββββββββββββββββββββββββ
β AppReducer β
β ββ MapFeature β
β β ββ Action: updateStarPosition β
β β ββ State: currentCoordinate β
β ββ PassportFeature β
β β ββ Action: addObservation β
β β ββ State: observations[] β
β ββ SettingsFeature β
β ββ Action: updateBirthDate β
β ββ State: userPreferences β
βββββββββββββββββββββββββββββββββββββββ
let locationStream = locationManager.asObservable()
.throttle(.milliseconds(500), scheduler: MainScheduler.instance)
.map { location in CLLocationCoordinate2D(...) }
let timerStream = Observable<Int>.interval(.seconds(5), scheduler: MainScheduler.instance)
.flatMap { _ in calculateStarPosition() }ββββββββββββββββββββββββββββββββββ
β ArchiveViewController β
β (UIKit + ReactorKit) β
ββ Reactor: ArchiveReactor β
β ββ State: records[], filter β
β ββ Action: loadRecords, sort β
β ββ Mutation: updateRecords β
ββββββββββββββββββββββββββββββββββ
| Market | Concept | Tone |
|---|---|---|
| π°π· Korea | "μλ‘μ κ°μ±" β λ³μ΄ 건λ€λ μμ μλ‘ | λ―Έλλ©, λ€ν¬ λΈλ£¨, μμ ν μ€νΈ |
| π―π΅ Japan | "μ€λμ λ³μ΄μΈ" β λ³μ μμΉλ‘ λν€ μ»¬λ¬ μ μ | κ·μ¬μ΄ μμ΄μ½, νμ€ν , μ μ±μ ν€ |
| πΊπΈ USA | "Precision Star Tracking" β μ²λ¬Έν λ°μ΄ν° κ°μ‘° | Sci-Fi UI, ν ν¬λ컬 λ©νΈλ¦ |
- Location: νμ μ¬μ©(Always) κΆνμΌλ‘ λ°±κ·ΈλΌμ΄λ μΆμ (νμμ)
- Data: λͺ¨λ κ΄μΈ‘ κΈ°λ‘μ λλ°μ΄μ€ λ‘컬 μ μ₯ (ν΄λΌμ°λ λ―ΈλκΈ°)
- Offline: SwiftAA μμ§μΌλ‘ μΈν°λ· μμ΄λ μ λ° κ³μ° κ°λ₯
- Battery: 5μ΄ μΈν°λ² μμΉ μ λ°μ΄νΈ, Widgetμ 15λΆ λ¦¬νλ μ
| λ¬Έμ | ν΄κ²° λ°©λ² |
|---|---|
| μΌμ λ°μ΄ν° λ Έμ΄μ¦ | RxSwift throttle + debounce |
| μν 볡μ‘λ μ¦κ° | TCAμ Scopeλ‘ Feature λΆλ¦¬ |
| λ κ°μ§ μν€ν μ²(TCA + RK) νΌμ© | λͺ νν λͺ¨λ κ²½κ³: Map/Settingsλ TCA, Archiveλ RK |
| κ³ λΉμ© μ²λ¬Έ API | SwiftAA μ€νλΌμΈ κ³μ° (μ μ§λΉ 0μ) |
| λ°±κ·ΈλΌμ΄λ μ λ°μ΄νΈ | RxSwift Observable + BGTask μ‘°ν© |
- iOS 16.0+
- Swift 5.9+
- CocoaPods / SPM
pod install
# or
swift package manager// BirthStarManager.swift
let manager = BirthStarManager()
manager.setBirthDate(Date(...))
manager.startTracking() // RxSwift μ€νΈλ¦Ό μμ
// MapViewμμ κ΄μ°°
manager.starPositionStream
.sink { coordinate in
self.mapView.setCenter(coordinate)
}
.store(in: &cancellables)Yunseul/
βββ App/
β βββ YunseulApp.swift
β βββ AppDelegate.swift
βββ Features/
β βββ Map/
β β βββ MapReducer.swift
β β βββ MapView.swift
β β βββ MapViewModel.swift
β βββ Passport/
β β βββ PassportReducer.swift
β β βββ PassportListView.swift
β βββ Archive/
β β βββ ArchiveViewController.swift
β β βββ ArchiveReactor.swift
β β βββ ArchiveCell.swift
β βββ Settings/
β βββ SettingsReducer.swift
β βββ SettingsView.swift
βββ Services/
β βββ BirthStarManager.swift
β βββ LocationService.swift
β βββ NotificationService.swift
βββ Models/
β βββ BirthStar.swift
β βββ ObservationRecord.swift
β βββ CapturedMoment.swift
βββ Utilities/
β βββ SwiftAAWrapper.swift
β βββ CoordinateConverter.swift
βββ Resources/
βββ Assets.xcassets
βββ Localizable.strings
- Map Screen: μΈκ³ μ§λ μ λ°μ§μ΄λ λ³ (μ€μκ° μ λλ©μ΄μ )
- Passport List: κΈ°λ‘ν μκ°λ€μ μΉ΄λ νμμΌλ‘ μκ°μ μ λ ¬
- Notification: λ³μ΄ νΉλ³ν μ₯μλ₯Ό μ§λ λ λ°λ»ν λ©μμ§
- Settings: μμΌ μ€μ , μλ¦Ό μ§μ λ±λ‘, λ€ν¬λͺ¨λ ν κΈ
Pull Requestλ μΈμ λ νμν©λλ€.
μ΄μ μ κΈ° μ λ€μμ ν¬ν¨ν΄μ£ΌμΈμ:
- λ²κ·Έ μ¬ν λ°©λ²
- μμ vs μ€μ λμ
- λλ°μ΄μ€ & iOS λ²μ
MIT License Β© 2026 Yunseul Project
λ³μ μΈμ λ μ°λ¦¬ κ³μ μλ€.
μ°λ¦¬κ° μ¬λ €λ€λ³΄μ§ μμλ,
λκ΅°κ°μ νμμΌμ μ‘°μ©ν λΉμΆκ³ μλ€.
λΉμ μ λ³, μ§κΈ μ΄λλ₯Ό ν₯ν΄ κ°κ³ μλμ?