์ค๋์ ๋ ์จ์ ํจ๊ป ํ๋ฃจ๋ฅผ ๊ธฐ๋กํ์ธ์ โ ๋ ์จ ๊ธฐ๋ฐ ์ผ๊ธฐ์ฅ, ์์ ฏ, ๋ค๊ตญ์ด๋ฅผ ์ง์ํ๋ ๊ฐ์ธ ๋ค์ด์ด๋ฆฌ ์ฑ์ ๋๋ค.
| ๋ฉ์ธ ํ๋ฉด | ์ผ๊ธฐ ์์ฑ | ์ผ๊ธฐ ์์ธ |
|---|---|---|
![]() |
![]() |
![]() |
| ์ผ๊ธฐ ๋ชฉ๋ก | ์ค์ | ๋คํฌ๋ชจ๋ ๋ฉ์ธํ๋ฉด |
|---|---|---|
![]() |
![]() |
![]() |
| ์์ ฏ (Small) | ์์ ฏ (Medium) | ์์ ฏ (Large) |
|---|---|---|
![]() |
![]() |
![]() |
- SwiftUI โ ์ ์ธํ UI๋ก ์ ์ฒด ํ๋ฉด์ ๊ตฌ์ฑํ์ต๋๋ค. FSCalendar๋ UIViewRepresentable + Coordinator ํจํด์ผ๋ก SwiftUI์ ํตํฉํ์ต๋๋ค.
- SwiftUI ๊ธฐ๋ณธ ๊ตฌ์กฐ (View + ObservableObject) โ View๊ฐ
@State/@Binding์ผ๋ก ๋ก์ปฌ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ ,@EnvironmentObject๋ก ๊ณต์ ์ํ(Manager/Service)์ ์ ๊ทผํฉ๋๋ค. SwiftDataManager๊ฐ ๋ฐ์ดํฐ CRUD๋ฅผ, WeatherServiceยทFontManagerยทNotificationManager ๋ฑ Service ๊ณ์ธต์ด ๋น์ฆ๋์ค ๋ก์ง์ ๊ฐ๊ฐ ๋ถ๋ฆฌํ์ฌ ๋ด๋นํฉ๋๋ค.
- SwiftData โ
@Model๋งคํฌ๋ก ๊ธฐ๋ฐ์ผ๋ก ์ผ๊ธฐ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. App Group ๊ณต์ ModelContainer๋ฅผ ํตํด ๋ฉ์ธ ์ฑ๊ณผ ์์ ฏ์ด ๋์ผํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฝ๊ณ ์๋๋ค.
- WeatherKit โ Apple์ ๋ค์ดํฐ๋ธ ๋ ์จ API๋ฅผ async/await๋ก ํธ์ถํฉ๋๋ค. Combine ํ์ด๋จธ๋ฅผ ํตํด 10๋ถ๋ง๋ค ์๋ ๊ฐฑ์ ํ๋ฉฐ, CLLocationManager๋ก ํ์ฌ ์์น์ ๋ ์จ์ ์ฃผ์๋ฅผ ํ์ํฉ๋๋ค.
- WidgetKit โ Small/Medium/Large 3๊ฐ์ง ์ฌ์ด์ฆ๋ฅผ ์ง์ํฉ๋๋ค. Large ์์ ฏ์๋ ์บ๋ฆฐ๋ ๊ทธ๋ฆฌ๋๋ฅผ ํ์ํ๋ฉฐ, ๋ฅ๋งํฌ(
diary://compose)๋ก ํญ ์ ์ผ๊ธฐ ์์ฑ ํ๋ฉด์ผ๋ก ์ง์ ์ด๋ํฉ๋๋ค.
- String Catalog โ ํ๊ตญ์ด, ์์ด, ์ผ๋ณธ์ด 3๊ฐ ์ธ์ด๋ฅผ ์ง์ํฉ๋๋ค. ์ฑ ์ฌ์์ ์์ด ์ค์ ์์ ์ฆ์ ์ธ์ด๋ฅผ ์ ํํ ์ ์์ผ๋ฉฐ, ์์ ฏ์๋ App Group UserDefaults๋ฅผ ํตํด ์ธ์ด ์ค์ ์ด ๋๊ธฐํ๋ฉ๋๋ค.
- UserNotifications โ ๋งค์ผ ์คํ 11์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฌ์ฉ์๊ฐ ์ผ๊ธฐ๋ฅผ ์์ฑํ์ง ์์์ผ๋ฉด ๋ฆฌ๋ง์ธ๋ ์๋ฆผ์ ๋ณด๋ ๋๋ค.
- PhotosUI (PhotosPicker) โ ์ฌ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๋ณต์ ์ด๋ฏธ์ง๋ฅผ ์ ํํ๊ณ , ํ์ผ ์์คํ ์ ์ ์ฅํ์ฌ SwiftData์๋ ํ์ผ๋ช ๋ง ๊ธฐ๋กํฉ๋๋ค.
graph LR
subgraph View
V["SwiftUI Views<br/>@State ยท @Binding"]
end
subgraph Manager
SDM["SwiftDataManager<br/>@MainActor"]
end
subgraph Service
WS[WeatherService]
FM[FontManager]
NM[NotificationManager]
end
subgraph Data
SD[SwiftData]
FS[FileSystem]
WK[WeatherKit]
CL[CoreLocation]
end
V -- "@EnvironmentObject" --> SDM
V -- "@EnvironmentObject" --> WS
V -- "@EnvironmentObject" --> FM
SDM -. "@Published" .-> V
WS -. "@Published" .-> V
SDM --> SD
SDM --> FS
WS --> WK
WS --> CL
SwiftUI์ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ํ๋ฆ์ ๋ฐ๋ฆ
๋๋ค. View๋ @State๋ก ๋ก์ปฌ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ , ์ฑ ์ ์ฒด์์ ๊ณต์ ๊ฐ ํ์ํ ์ํ๋ ObservableObject๋ฅผ ์ฑํํ Manager/Service ํด๋์ค์์ @Published๋ก ์ ๊ณตํฉ๋๋ค. View๋ @EnvironmentObject๋ฅผ ํตํด ์ด๋ค์ ์ ๊ทผํ๋ฉฐ, ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ SwiftUI๊ฐ ์๋์ผ๋ก UI๋ฅผ ๊ฐฑ์ ํฉ๋๋ค. ๋น์ฆ๋์ค ๋ก์ง(๋ ์จ ์กฐํ, ํฐํธ ๊ด๋ฆฌ, ์๋ฆผ ์ค์ผ์ค๋ง ๋ฑ)์ ๊ฐ Service ํด๋์ค์ ๋ถ๋ฆฌํ์ฌ View์ ์ฑ
์์ ์ต์ํํ์ต๋๋ค.
graph LR
subgraph Main App
MA[DiaryApp]
MC[SharedModelContainer]
MUD[UserDefaults - App Group]
end
subgraph Widget Extension
WE[DiaryWidget]
WC[SharedModelContainer]
WUD[UserDefaults - App Group]
end
subgraph Shared Storage
AG[(App Group Container)]
end
MA --> MC
MC --> AG
MA --> MUD
MUD --> AG
WE --> WC
WC --> AG
WE --> WUD
WUD --> AG
๋ฉ์ธ ์ฑ๊ณผ ์์ ฏ์ App Group(group.com.reimos7.goodd.diary)์ ํตํด SwiftData ๋ฐ์ดํฐ๋ฒ ์ด์ค์ UserDefaults(์ธ์ด ์ค์ )๋ฅผ ๊ณต์ ํฉ๋๋ค. ์์ ฏ์ ๋ณ๋ ํ๋ก์ธ์ค๋ก ์คํ๋๋ฏ๋ก, ๋ฉ์ธ ์ฑ์์ ์ผ๊ธฐ๋ฅผ ์ถ๊ฐ/์์ /์ญ์ ํ ๋๋ง๋ค WidgetCenter.shared.reloadAllTimelines()๋ก ์์ ฏ ๊ฐฑ์ ์ ์์ฒญํฉ๋๋ค.
sequenceDiagram
participant UI as WeatherView
participant WS as WeatherService
participant CL as CLLocationManager
participant GC as CLGeocoder
participant WK as WeatherKit API
UI->>WS: @EnvironmentObject ๋ฐ์ธ๋ฉ
WS->>CL: requestLocation()
CL-->>WS: didUpdateLocations (์๋/๊ฒฝ๋)
WS->>GC: reverseGeocodeLocation (์ฑ ์ธ์ด locale)
GC-->>WS: ์ฃผ์ (๊ตฌ + ๋)
Note over WS,GC: ํ๊ธ fallback ๊ฐ์ง
alt ์ฑ ์ธ์ด โ ํ๊ตญ์ด && ์ฃผ์์ ํ๊ธ ํฌํจ
WS->>GC: ์์ด locale๋ก ์ฌ์์ฒญ
GC-->>WS: ๋ก๋ง์ ์ฃผ์
end
WS->>WK: weather(for: location)
WK-->>WS: Weather ๋ฐ์ดํฐ
WS->>WS: CurrentWeather ๋ชจ๋ธ ์์ฑ
WS-->>UI: @Published currentWeather ์
๋ฐ์ดํธ
์์น ๊ถํ ํ๋ ํ CLLocationManager์์ ์ขํ๋ฅผ ๋ฐ์, CLGeocoder๋ก ์ฃผ์๋ฅผ ๋ณํํ๊ณ WeatherKit์ผ๋ก ๋ ์จ๋ฅผ ์กฐํํฉ๋๋ค. CLGeocoder๊ฐ ํน์ ์ธ์ด๋ก ์ฃผ์๋ฅผ ๋ฒ์ญํ์ง ๋ชปํ ๊ฒฝ์ฐ(์: ํ๊ตญ ์ฃผ์๋ฅผ ์ผ๋ณธ์ด๋ก ์์ฒญ), ํ๊ธ ๊ฐ์ง ํ ์์ด locale๋ก ์ฌ์์ฒญํ์ฌ ๋ก๋ง์ ์ฃผ์๋ฅผ ํ์ํฉ๋๋ค.
sequenceDiagram
participant UI as LanguageView
participant AS as @AppStorage
participant DA as DiaryApp
participant WS as WeatherService
participant WG as Widget
UI->>AS: appLanguage = "ja"
AS-->>DA: .environment(\.locale) ์๋ ๋ฐ์
DA-->>UI: ์ ์ฒด UI ์ผ๋ณธ์ด๋ก ์ฌ๋ ๋๋ง
UI->>WG: App Group UserDefaults ๋๊ธฐํ
UI->>WG: WidgetCenter.reloadAllTimelines()
UI->>WS: updateWeatherManually()
WS->>WS: ์ locale๋ก ๋ ์จ + ์ฃผ์ ์ฌ์์ฒญ
WS-->>UI: ๋ ์จ ์ค๋ช
ยท์ฃผ์ ์ผ๋ณธ์ด๋ก ๊ฐฑ์
์ฌ์ฉ์๊ฐ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ๋ฉด @AppStorage๊ฐ ์ฆ์ ๋ฐ์๋์ด DiaryApp์ .environment(\.locale)์ด ์๋์ผ๋ก ์ ์ธ์ด๋ฅผ ์ ์ฉํฉ๋๋ค. ์ฑ ์ฌ์์ ์์ด ์ ์ฒด UI๊ฐ ์ฆ์ ์ ์ธ์ด๋ก ๋ค์ ๋ ๋๋ง๋๋ฉฐ, ์์ ฏ์๋ App Group์ ํตํด ์ธ์ด ์ค์ ์ด ๋๊ธฐํ๋ฉ๋๋ค.
ํ์ฌ ์์น์ ์ค์๊ฐ ๋ ์จ(์์ด์ฝ, ๊ธฐ์จ, ์ต๊ณ ยท์ต์ ์จ๋, ์ผ์ถยท์ผ๋ชฐ)๋ฅผ ํ์ํ๋ฉฐ, ์ผ๊ธฐ ์ ์ฅ ์ ๊ทธ ๋ ์ ๋ ์จ ์์ด์ฝ์ด ํจ๊ป ๊ธฐ๋ก๋ฉ๋๋ค. ๋ ์จ ์ค๋ช ์ ํ๊ตญ์ดยท์์ดยท์ผ๋ณธ์ด๋ก ๋ค๊ตญ์ด ๋ฒ์ญ๋ฉ๋๋ค.
PhotosPicker๋ฅผ ํตํด ์ด๋ฏธ์ง๋ฅผ ์ ํํ๊ณ ์ผ๊ธฐ์ ์ฒจ๋ถํ ์ ์์ต๋๋ค. ์ด๋ฏธ์ง๋ ํ์ผ ์์คํ ์ ์ ์ฅ๋๋ฉฐ SwiftData์๋ ํ์ผ๋ช ๋ง ๊ธฐ๋กํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถ๋ด์ ์ต์ํํฉ๋๋ค. ์ ์ฒด ํ๋ฉด ์ค ๋ทฐ์ ๋กฑํ๋ ์ค ์ญ์ ๋ฅผ ์ง์ํฉ๋๋ค.
FSCalendar๋ฅผ UIViewRepresentable๋ก ํตํฉํ์ฌ ์๋ณ ์ผ๊ธฐ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์ธํ ์ ์์ต๋๋ค. ์ผ๊ธฐ๊ฐ ์๋ ๋ ์ง์ ๋ง์ปค๊ฐ ํ์๋๋ฉฐ, ๋ ์ง๋ฅผ ํญํ๋ฉด ํด๋น ๋ ์ง์ ์ผ๊ธฐ๋ฅผ ์์ฑํ๊ฑฐ๋ ์กฐํํ ์ ์์ต๋๋ค.
WidgetKit์ผ๋ก 3๊ฐ์ง ์ฌ์ด์ฆ์ ์์ ฏ์ ์ ๊ณตํฉ๋๋ค. Small์ ์ค๋์ ์ผ๊ธฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, Medium์ ๋ ์จ ์์ด์ฝ๊ณผ ์ผ๊ธฐ ๋ด์ฉ, Large๋ ์บ๋ฆฐ๋ ๊ทธ๋ฆฌ๋๋ก ์ด๋ฒ ๋ฌ ์ผ๊ธฐ ํํฉ์ ๋ณด์ฌ์ค๋๋ค. ์์ ฏ ํญ ์ ๋ฅ๋งํฌ๋ก ์ผ๊ธฐ ์์ฑ ํ๋ฉด์ ์ง์ ์ง์ ํฉ๋๋ค.
์ฑ ๋ด ์ค์ ์์ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์ฌ์์ ์์ด ์ฆ์ ์ ์ฒด UI๊ฐ ์ ํ๋ฉ๋๋ค. String Catalog ๊ธฐ๋ฐ์ผ๋ก 200๊ฐ ์ด์์ ๋ฌธ์์ด์ด ๋ฒ์ญ๋์ด ์์ผ๋ฉฐ, ๋ ์ง ํฌ๋งทยท๋ ์จ ์ค๋ช ยท์์ ฏ๊น์ง ๋ชจ๋ ์ ํํ ์ธ์ด๋ก ํ์๋ฉ๋๋ค.
๋ผ์ดํธ,๋คํฌ,์์คํ 3๊ฐ์ง ํ ๋ง ๋ชจ๋์ 4์ข ์ ์ปค์คํฐ ํฐํธ๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ฌธ์ ์์ ฏ์ ๋ฉ์ธ ์ฑ๊ณผ ๋ณ๋ ํ๋ก์ธ์ค๋ก ์คํ๋๊ธฐ ๋๋ฌธ์, ๋ฉ์ธ ์ฑ์ SwiftData ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ ๊ทผํ ์ ์์์ต๋๋ค. ์ผ๊ธฐ๋ฅผ ์์ฑํด๋ ์์ ฏ์ ๋ฐ์๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
ํด๊ฒฐ
App Group(group.com.reimos7.goodd.diary)์ ํตํด ๊ณต์ ModelContainer๋ฅผ ์์ฑํ๊ณ , ๋ฉ์ธ ์ฑ๊ณผ ์์ ฏ ๋ชจ๋ ๋์ผํ ์ ์ฅ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๋๋ก ์ค๊ณํ์ต๋๋ค. ์ผ๊ธฐ CRUD ์๋ง๋ค WidgetCenter.shared.reloadAllTimelines()๋ฅผ ํธ์ถํ์ฌ ์์ ฏ์ ์ฆ์ ๊ฐฑ์ ํฉ๋๋ค.
enum SharedModelContainer {
static let appGroupIdentifier = "group.com.reimos7.goodd.diary"
static func create() -> ModelContainer {
let schema = Schema([Diary.self])
let config = ModelConfiguration(
schema: schema,
groupContainer: .identifier(appGroupIdentifier)
)
return try! ModelContainer(for: schema, configurations: [config])
}
}2. CLGeocoder ์ผ๋ณธ์ด locale์์ ํ๊ตญ ์ฃผ์๊ฐ ๋ฒ์ญ๋์ง ์๋ ๋ฌธ์ โ ํ๊ธ ๊ฐ์ง + ์์ด fallback
๋ฌธ์ ์ฑ ์ธ์ด๋ฅผ ์ผ๋ณธ์ด๋ก ์ค์ ํ ์ํ์์ ํ๊ตญ์ ์์นํ ๊ฒฝ์ฐ, CLGeocoder์ ์ผ๋ณธ์ด locale์ ์ ๋ฌํด๋ ํ๊ตญ ์ฃผ์๊ฐ ํ๊ธ("๊ฐ๋จ๊ตฌ ์ผ์ฑ๋")๋ก ๊ทธ๋๋ก ๋ฐํ๋์์ต๋๋ค. Apple์ ์ง๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ๊ตญ ์ฃผ์์ ์ผ๋ณธ์ด ๋ฒ์ญ์ด ์กด์ฌํ์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
ํด๊ฒฐ CLGeocoder ์๋ต์์ ํ๊ธ ํฌํจ ์ฌ๋ถ๋ฅผ ์ ๊ทํํ์์ผ๋ก ๊ฐ์งํ๊ณ , ์ฑ ์ธ์ด๊ฐ ํ๊ตญ์ด๊ฐ ์๋๋ฐ ํ๊ธ์ด ํฌํจ๋ ๊ฒฝ์ฐ ์์ด locale๋ก ์ฌ์์ฒญํ์ฌ ๋ก๋ง์ ์ฃผ์("Gangnam-gu Samseong-dong")๋ฅผ ํ์ํ๋ fallback ์ ๋ต์ ์ ์ฉํ์ต๋๋ค.
if currentLangCode != "ko" && address.containsKorean {
let enGeocoder = CLGeocoder()
let enPlacemarks = try await enGeocoder.reverseGeocodeLocation(
location, preferredLocale: Locale(identifier: "en")
)
if let enPlacemark = enPlacemarks.first,
let enGu = enPlacemark.locality,
let enDong = enPlacemark.subLocality {
return "\(enGu) \(enDong)"
}
}๋ฌธ์ ์ผ๋ฐ์ ์ผ๋ก iOS ์ฑ์ ์ธ์ด ๋ณ๊ฒฝ์ ์ฑ ์ฌ์์์ด ํ์ํฉ๋๋ค. ํ์ง๋ง ์ผ๊ธฐ ์ฑ ํน์ฑ์ ์์ฑ ์ค ๋ฐ์ดํฐ ์ ์ค ์์ด ์ฆ์ ์ ํ๋์ด์ผ ํ์ต๋๋ค.
ํด๊ฒฐ
@AppStorage("appLanguage")๋ก ์ ํ๋ ์ธ์ด๋ฅผ ์ ์ฅํ๊ณ , DiaryApp์ WindowGroup์ .environment(\.locale, Locale(identifier: appLanguage))๋ฅผ ์ ์ฉํ์ต๋๋ค. @AppStorage ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด SwiftUI๊ฐ ์๋์ผ๋ก ์ ์ฒด ๋ทฐ ํธ๋ฆฌ๋ฅผ ์ locale๋ก ๋ค์ ๋ ๋๋งํฉ๋๋ค. Foundation API(DateFormatter, CLGeocoder ๋ฑ)์๋ appLocale computed property๋ฅผ ํตํด ๋ช
์์ ์ผ๋ก locale์ ์ ๋ฌํ์ฌ SwiftUI ์ธ๋ถ์์๋ ์ธ์ด๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋ฐ์๋๋๋ก ํ์ต๋๋ค.
// DiaryApp.swift
.environment(\.locale, Locale(identifier: appLanguage))
// Foundation API์ฉ (DateFormatter, CLGeocoder ๋ฑ)
var appLocale: Locale {
let code = UserDefaults.standard.string(forKey: "appLanguage") ?? "en"
return Locale(identifier: code)
}๋ฌธ์ WeatherKit์ Apple Developer Program ๋ฉค๋ฒ์ญ ๊ธฐ์ค ์ 500,000ํ ํธ์ถ์ด ๊ฐ๋ฅํ์ง๋ง, ์ฌ์ฉ์ ์๊ฐ ๋์ด๋๋ฉด ํธ์ถ๋์ด ๊ธ์ฆํ ์ ์์ต๋๋ค. ๋ํ ๋ถํ์ํ API ํธ์ถ์ ๋ฐฐํฐ๋ฆฌ ์๋ชจ์ ๋คํธ์ํฌ ํธ๋ํฝ ๋ญ๋น๋ก ์ด์ด์ง๋๋ค.
ํด๊ฒฐ
Combine์ Timer.publish(every: 600)์ผ๋ก 10๋ถ ๊ฐ๊ฒฉ ์๋ ๊ฐฑ์ ํ์ด๋จธ๋ฅผ ์ค์ ํ๋, .main RunLoop์์ ์คํํ์ฌ ๋ฐฑ๊ทธ๋ผ์ด๋์์๋ ์๋์ผ๋ก ์ ์ง๋๋๋ก ํ์ต๋๋ค. ์ด๋ก์จ ์ฑ์ด ํฌ๊ทธ๋ผ์ด๋ ์ํ์ผ ๋๋ง API๋ฅผ ํธ์ถํ์ฌ, ๋ถํ์ํ ํธ์ถ์ ๋ฐฉ์งํ๋ฉด์๋ ๋ ์จ ์ ๋ณด์ ์ ์ ๋๋ฅผ ์ ์งํฉ๋๋ค.
timer = Timer.publish(every: 600, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.updateWeatherManually()
}๋ฌธ์
์ผ๊ธฐ์ ์ฒจ๋ถ๋ ์ฌ์ง์ SwiftData์ Data ํ์
์ผ๋ก ์ง์ ์ ์ฅํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ํฌ๊ธฐ๊ฐ ๊ธ๊ฒฉํ ์ฆ๊ฐํ๊ณ , ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๊ณผ๋ํด์ง๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
ํด๊ฒฐ
์ด๋ฏธ์ง๋ ์ฑ์ Documents ๋๋ ํ ๋ฆฌ์ ํ์ผ๋ก ์ ์ฅํ๊ณ , SwiftData์๋ ํ์ผ๋ช
(String)๋ง ๊ธฐ๋กํ๋ ์ ๋ต์ ์ฑํํ์ต๋๋ค. ์ ๋ ๊ฒฝ๋ก ๋์ ํ์ผ๋ช
๋ง ์ ์ฅํ์ฌ ์ฑ ์ฌ์ค์น ์์๋ ๊ฒฝ๋ก ๋ถ์ผ์น ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํฉ๋๋ค. ์ผ๊ธฐ ์ญ์ ์์๋ ์ฐ๊ฒฐ๋ ์ด๋ฏธ์ง ํ์ผ๋ ํจ๊ป ์ญ์ ํ์ฌ ์ ์ฅ ๊ณต๊ฐ ๋์๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
@Model
class Diary {
var content: String
var date: Date
var imagePath: [String]? // ํ์ผ๋ช
๋ง ์ ์ฅ (์ ๋๊ฒฝ๋ก X)
var currentWeatherImage: String?
}
// ์ผ๊ธฐ ์ญ์ ์ ์ด๋ฏธ์ง ํ์ผ๋ ํจ๊ป ์ ๊ฑฐ
func deleteDiaryData(entry: Diary) {
if let paths = entry.imagePath {
for path in paths {
FileManagerSet.deleteImageFromFileManager(fileName: path)
}
}
context.delete(entry)
}Diary/
โโ Application/
โ โโ DiaryApp.swift # ์ฑ ์ง์
์ (ํ
๋ง/์ธ์ด/๋ฅ๋งํฌ ์ค์ )
โโ Model/
โ โโ Model.swift # @Model Diary (SwiftData)
โ โโ CurrentWeather.swift # ๋ ์จ ๋๋ฉ์ธ ๋ชจ๋ธ
โ โโ CodableCurrentWeather.swift # Codable ๋ ์จ DTO
โ โโ SwiftDataManager.swift # CRUD ๋งค๋์ (@MainActor)
โ โโ SharedModelContainer.swift # App Group ๊ณต์ ์ปจํ
์ด๋
โโ View/
โ โโ MainView.swift # ํ (๋ ์จ + ์บ๋ฆฐ๋ + ์์ฑ ๋ฒํผ)
โ โโ ComposeView.swift # ์ผ๊ธฐ ์์ฑ (์ฌ์ง + ๋ ์จ)
โ โโ CalendarComposeView.swift # ํน์ ๋ ์ง ์ผ๊ธฐ ์์ฑ
โ โโ EditComposeView.swift # ์ผ๊ธฐ ์์
โ โโ DetailView.swift # ์ผ๊ธฐ ์์ธ ์กฐํ
โ โโ ListView.swift # ์ผ๊ธฐ ๋ชฉ๋ก (์ ๋ ฌ/๊ฒ์)
โ โโ SearchView.swift # ํค์๋ ๊ฒ์
โ โโ CalendarSetView.swift # FSCalendar ํตํฉ
โ โโ WeatherView.swift # ๋ ์จ ์นด๋
โ โโ SettingView.swift # ์ค์ ๋ฉ๋ด
โ โโ ThemeView.swift # ํ
๋ง ์ ํ
โ โโ FontView.swift # ํฐํธ ์ ํ
โ โโ NotificationView.swift # ์๋ฆผ ์ค์
โ โโ LanguageView.swift # ์ธ์ด ์ ํ
โ โโ PhotoPickerView.swift # ์ฌ์ง ์ ํ
โ โโ FullScreenImageView.swift # ์ด๋ฏธ์ง ์ค ๋ทฐ
โ โโ LaunchScreenView.swift # ์คํ๋์ ํ๋ฉด
โโ Service/
โ โโ WeatherService.swift # ๋ ์จ + Combine ํ์ด๋จธ
โ โโ WeatherService+Location.swift # CLLocationManager ๋ธ๋ฆฌ๊ฒ์ดํธ
โ โโ WeatherService+Api.swift # WeatherKit API ํธ์ถ
โ โโ NotificationManager.swift # ๋ฆฌ๋ง์ธ๋ ์๋ฆผ
โ โโ FontManager.swift # ์ปค์คํ
ํฐํธ ๊ด๋ฆฌ
โ โโ PhotoPermissionManager.swift # ์ฌ์ง ๊ถํ
โ โโ EmailManager.swift # ์ธ์ฑ ์ด๋ฉ์ผ
โ โโ CaptureManager.swift # ์คํฌ๋ฆฐ์ท ์ ํธ
โโ Extension/
โ โโ DateFormatter+dateFormatter.swift # ๋ค๊ตญ์ด ๋ ์ง ํฌ๋งท
โ โโ String+WeatherSymbol.swift # ๋ ์จ โ SF Symbol ๋งคํ
โ โโ Double+TemperatureString.swift # ์จ๋ ํฌ๋งท
โ โโ View+Function.swift # ์ปค์คํ
ํค๋ณด๋ ํด๋ฐ
โโ Font/ # ์ปค์คํ
ํฐํธ ํ์ผ (4์ข
)
โโ Localization/
โ โโ ko.lproj/ # ํ๊ตญ์ด
โ โโ en.lproj/ # ์์ด
โ โโ ja.lproj/ # ์ผ๋ณธ์ด
โ
โโ DiaryWidget/ # ์์ ฏ ์ต์คํ
์
โ โโ DiaryWidget.swift # Small/Medium/Large ์์ ฏ
โ โโ DiaryWidgetBundle.swift # ์์ ฏ ์ง์
์
โ
โโ Localizable.xcstrings # String Catalog (200+ ํค)
์ด ํ๋ก์ ํธ๋ ๊ฐ์ธ ํฌํธํด๋ฆฌ์ค ํ๋ก์ ํธ์ด๋ฉฐ, ์์ ์ ์ฌ์ฉ์ ๊ธ์งํฉ๋๋ค.








