Skip to content

Reimos7/Diary

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

90 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

GoodDi App Icon

GoodDi (๊ตฟ๋””)

์˜ค๋Š˜์˜ ๋‚ ์”จ์™€ ํ•จ๊ป˜ ํ•˜๋ฃจ๋ฅผ ๊ธฐ๋กํ•˜์„ธ์š” โ€” ๋‚ ์”จ ๊ธฐ๋ฐ˜ ์ผ๊ธฐ์žฅ, ์œ„์ ฏ, ๋‹ค๊ตญ์–ด๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฐœ์ธ ๋‹ค์ด์–ด๋ฆฌ ์•ฑ์ž…๋‹ˆ๋‹ค.


Screenshots

๋ฉ”์ธ ํ™”๋ฉด ์ผ๊ธฐ ์ž‘์„ฑ ์ผ๊ธฐ ์ƒ์„ธ
1 ๋ฉ”์ธํ™”๋ฉด(๋ผ์ดํŠธ๋ชจ๋“œ) 4 ์ผ๊ธฐ ์ž‘์„ฑํ™”๋ฉด 2 แ„ƒแ…ตแ„แ…ฆแ„‹แ…ตแ†ฏ แ„’แ…ชแ„†แ…งแ†ซ
์ผ๊ธฐ ๋ชฉ๋ก ์„ค์ • ๋‹คํฌ๋ชจ๋“œ ๋ฉ”์ธํ™”๋ฉด
3 ๋ชฉ๋ก ํ™”๋ฉด 6  ์„ค์ •ํ™”๋ฉด 5 ๋ฉ”์ธํ™”๋ฉด(๋‹คํฌ๋ชจ๋“œ)
์œ„์ ฏ (Small) ์œ„์ ฏ (Medium) ์œ„์ ฏ (Large)
Simulator Screenshot - iPhone 17 - 2026-03-07 at 16 11 51 Simulator Screenshot - iPhone 17 - 2026-03-07 at 16 11 59 Simulator Screenshot - iPhone 17 - 2026-03-07 at 16 12 14

Tech Stack

UI

  • SwiftUI โ€” ์„ ์–ธํ˜• UI๋กœ ์ „์ฒด ํ™”๋ฉด์„ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. FSCalendar๋Š” UIViewRepresentable + Coordinator ํŒจํ„ด์œผ๋กœ SwiftUI์— ํ†ตํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค.

Architecture

  • SwiftUI ๊ธฐ๋ณธ ๊ตฌ์กฐ (View + ObservableObject) โ€” View๊ฐ€ @State/@Binding์œผ๋กœ ๋กœ์ปฌ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , @EnvironmentObject๋กœ ๊ณต์œ  ์ƒํƒœ(Manager/Service)์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค. SwiftDataManager๊ฐ€ ๋ฐ์ดํ„ฐ CRUD๋ฅผ, WeatherServiceยทFontManagerยทNotificationManager ๋“ฑ Service ๊ณ„์ธต์ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ฐ๊ฐ ๋ถ„๋ฆฌํ•˜์—ฌ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

Persistence

  • SwiftData โ€” @Model ๋งคํฌ๋กœ ๊ธฐ๋ฐ˜์œผ๋กœ ์ผ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. App Group ๊ณต์œ  ModelContainer๋ฅผ ํ†ตํ•ด ๋ฉ”์ธ ์•ฑ๊ณผ ์œ„์ ฏ์ด ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ฝ๊ณ  ์”๋‹ˆ๋‹ค.

Weather

  • WeatherKit โ€” Apple์˜ ๋„ค์ดํ‹ฐ๋ธŒ ๋‚ ์”จ API๋ฅผ async/await๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. Combine ํƒ€์ด๋จธ๋ฅผ ํ†ตํ•ด 10๋ถ„๋งˆ๋‹ค ์ž๋™ ๊ฐฑ์‹ ํ•˜๋ฉฐ, CLLocationManager๋กœ ํ˜„์žฌ ์œ„์น˜์˜ ๋‚ ์”จ์™€ ์ฃผ์†Œ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

Widget

  • WidgetKit โ€” Small/Medium/Large 3๊ฐ€์ง€ ์‚ฌ์ด์ฆˆ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. Large ์œ„์ ฏ์—๋Š” ์บ˜๋ฆฐ๋” ๊ทธ๋ฆฌ๋“œ๋ฅผ ํ‘œ์‹œํ•˜๋ฉฐ, ๋”ฅ๋งํฌ(diary://compose)๋กœ ํƒญ ์‹œ ์ผ๊ธฐ ์ž‘์„ฑ ํ™”๋ฉด์œผ๋กœ ์ง์ ‘ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

Localization

  • String Catalog โ€” ํ•œ๊ตญ์–ด, ์˜์–ด, ์ผ๋ณธ์–ด 3๊ฐœ ์–ธ์–ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์•ฑ ์žฌ์‹œ์ž‘ ์—†์ด ์„ค์ •์—์„œ ์ฆ‰์‹œ ์–ธ์–ด๋ฅผ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์œ„์ ฏ์—๋„ App Group UserDefaults๋ฅผ ํ†ตํ•ด ์–ธ์–ด ์„ค์ •์ด ๋™๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค.

Notification

  • UserNotifications โ€” ๋งค์ผ ์˜คํ›„ 11์‹œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜์œผ๋ฉด ๋ฆฌ๋งˆ์ธ๋” ์•Œ๋ฆผ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

Image

  • PhotosUI (PhotosPicker) โ€” ์‚ฌ์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ๋ณต์ˆ˜ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜๊ณ , ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ €์žฅํ•˜์—ฌ SwiftData์—๋Š” ํŒŒ์ผ๋ช…๋งŒ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

Architecture

1. View + ObservableObject + Service ๊ณ„์ธต ๋ถ„๋ฆฌ

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
Loading

SwiftUI์˜ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. View๋Š” @State๋กœ ๋กœ์ปฌ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ์•ฑ ์ „์ฒด์—์„œ ๊ณต์œ ๊ฐ€ ํ•„์š”ํ•œ ์ƒํƒœ๋Š” ObservableObject๋ฅผ ์ฑ„ํƒํ•œ Manager/Service ํด๋ž˜์Šค์—์„œ @Published๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. View๋Š” @EnvironmentObject๋ฅผ ํ†ตํ•ด ์ด๋“ค์— ์ ‘๊ทผํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ SwiftUI๊ฐ€ ์ž๋™์œผ๋กœ UI๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(๋‚ ์”จ ์กฐํšŒ, ํฐํŠธ ๊ด€๋ฆฌ, ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง ๋“ฑ)์€ ๊ฐ Service ํด๋ž˜์Šค์— ๋ถ„๋ฆฌํ•˜์—ฌ View์˜ ์ฑ…์ž„์„ ์ตœ์†Œํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.


2. ์œ„์ ฏ ๋ฐ์ดํ„ฐ ๊ณต์œ  (App Group + SwiftData)

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
Loading

๋ฉ”์ธ ์•ฑ๊ณผ ์œ„์ ฏ์€ App Group(group.com.reimos7.goodd.diary)์„ ํ†ตํ•ด SwiftData ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ UserDefaults(์–ธ์–ด ์„ค์ •)๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ์œ„์ ฏ์€ ๋ณ„๋„ ํ”„๋กœ์„ธ์Šค๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ, ๋ฉ”์ธ ์•ฑ์—์„œ ์ผ๊ธฐ๋ฅผ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œํ•  ๋•Œ๋งˆ๋‹ค WidgetCenter.shared.reloadAllTimelines()๋กœ ์œ„์ ฏ ๊ฐฑ์‹ ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.


3. ๋‚ ์”จ ๋ฐ์ดํ„ฐ ํ๋ฆ„ (WeatherKit + CoreLocation)

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 ์—…๋ฐ์ดํŠธ
Loading

์œ„์น˜ ๊ถŒํ•œ ํš๋“ ํ›„ CLLocationManager์—์„œ ์ขŒํ‘œ๋ฅผ ๋ฐ›์•„, CLGeocoder๋กœ ์ฃผ์†Œ๋ฅผ ๋ณ€ํ™˜ํ•˜๊ณ  WeatherKit์œผ๋กœ ๋‚ ์”จ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. CLGeocoder๊ฐ€ ํŠน์ • ์–ธ์–ด๋กœ ์ฃผ์†Œ๋ฅผ ๋ฒˆ์—ญํ•˜์ง€ ๋ชปํ•  ๊ฒฝ์šฐ(์˜ˆ: ํ•œ๊ตญ ์ฃผ์†Œ๋ฅผ ์ผ๋ณธ์–ด๋กœ ์š”์ฒญ), ํ•œ๊ธ€ ๊ฐ์ง€ ํ›„ ์˜์–ด locale๋กœ ์žฌ์š”์ฒญํ•˜์—ฌ ๋กœ๋งˆ์ž ์ฃผ์†Œ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.


4. ์•ฑ ๋‚ด ์–ธ์–ด ์ „ํ™˜ ํ๋ฆ„

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: ๋‚ ์”จ ์„ค๋ช…ยท์ฃผ์†Œ ์ผ๋ณธ์–ด๋กœ ๊ฐฑ์‹ 
Loading

์‚ฌ์šฉ์ž๊ฐ€ ์–ธ์–ด๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด @AppStorage๊ฐ€ ์ฆ‰์‹œ ๋ฐ˜์˜๋˜์–ด DiaryApp์˜ .environment(\.locale)์ด ์ž๋™์œผ๋กœ ์ƒˆ ์–ธ์–ด๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์•ฑ ์žฌ์‹œ์ž‘ ์—†์ด ์ „์ฒด UI๊ฐ€ ์ฆ‰์‹œ ์ƒˆ ์–ธ์–ด๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜๋ฉฐ, ์œ„์ ฏ์—๋„ App Group์„ ํ†ตํ•ด ์–ธ์–ด ์„ค์ •์ด ๋™๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค.


Key Features

1. ๋‚ ์”จ์™€ ํ•จ๊ป˜ ์ผ๊ธฐ ์ž‘์„ฑ

ํ˜„์žฌ ์œ„์น˜์˜ ์‹ค์‹œ๊ฐ„ ๋‚ ์”จ(์•„์ด์ฝ˜, ๊ธฐ์˜จ, ์ตœ๊ณ ยท์ตœ์ € ์˜จ๋„, ์ผ์ถœยท์ผ๋ชฐ)๋ฅผ ํ‘œ์‹œํ•˜๋ฉฐ, ์ผ๊ธฐ ์ €์žฅ ์‹œ ๊ทธ ๋‚ ์˜ ๋‚ ์”จ ์•„์ด์ฝ˜์ด ํ•จ๊ป˜ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. ๋‚ ์”จ ์„ค๋ช…์€ ํ•œ๊ตญ์–ดยท์˜์–ดยท์ผ๋ณธ์–ด๋กœ ๋‹ค๊ตญ์–ด ๋ฒˆ์—ญ๋ฉ๋‹ˆ๋‹ค.

2. ์‚ฌ์ง„ ์ฒจ๋ถ€ ์ผ๊ธฐ

PhotosPicker๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜๊ณ  ์ผ๊ธฐ์— ์ฒจ๋ถ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๋Š” ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ €์žฅ๋˜๋ฉฐ SwiftData์—๋Š” ํŒŒ์ผ๋ช…๋งŒ ๊ธฐ๋กํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€๋‹ด์„ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ํ™”๋ฉด ์คŒ ๋ทฐ์™€ ๋กฑํ”„๋ ˆ์Šค ์‚ญ์ œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

3. ์บ˜๋ฆฐ๋” ๊ธฐ๋ฐ˜ ์ผ๊ธฐ ๊ด€๋ฆฌ

FSCalendar๋ฅผ UIViewRepresentable๋กœ ํ†ตํ•ฉํ•˜์—ฌ ์›”๋ณ„ ์ผ๊ธฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๊ธฐ๊ฐ€ ์žˆ๋Š” ๋‚ ์งœ์— ๋งˆ์ปค๊ฐ€ ํ‘œ์‹œ๋˜๋ฉฐ, ๋‚ ์งœ๋ฅผ ํƒญํ•˜๋ฉด ํ•ด๋‹น ๋‚ ์งœ์˜ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ํ™ˆ ํ™”๋ฉด ์œ„์ ฏ (Small / Medium / Large)

WidgetKit์œผ๋กœ 3๊ฐ€์ง€ ์‚ฌ์ด์ฆˆ์˜ ์œ„์ ฏ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Small์€ ์˜ค๋Š˜์˜ ์ผ๊ธฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, Medium์€ ๋‚ ์”จ ์•„์ด์ฝ˜๊ณผ ์ผ๊ธฐ ๋‚ด์šฉ, Large๋Š” ์บ˜๋ฆฐ๋” ๊ทธ๋ฆฌ๋“œ๋กœ ์ด๋ฒˆ ๋‹ฌ ์ผ๊ธฐ ํ˜„ํ™ฉ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์œ„์ ฏ ํƒญ ์‹œ ๋”ฅ๋งํฌ๋กœ ์ผ๊ธฐ ์ž‘์„ฑ ํ™”๋ฉด์— ์ง์ ‘ ์ง„์ž…ํ•ฉ๋‹ˆ๋‹ค.

5. ๋‹ค๊ตญ์–ด ์ง€์› (ํ•œ๊ตญ์–ด / ์˜์–ด / ์ผ๋ณธ์–ด)

์•ฑ ๋‚ด ์„ค์ •์—์„œ ์–ธ์–ด๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ์žฌ์‹œ์ž‘ ์—†์ด ์ฆ‰์‹œ ์ „์ฒด UI๊ฐ€ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค. String Catalog ๊ธฐ๋ฐ˜์œผ๋กœ 200๊ฐœ ์ด์ƒ์˜ ๋ฌธ์ž์—ด์ด ๋ฒˆ์—ญ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๋‚ ์งœ ํฌ๋งทยท๋‚ ์”จ ์„ค๋ช…ยท์œ„์ ฏ๊นŒ์ง€ ๋ชจ๋‘ ์„ ํƒํ•œ ์–ธ์–ด๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

6. ์ปค์Šคํ„ฐ๋งˆ์ด์ง• (ํ…Œ๋งˆ / ํฐํŠธ)

๋ผ์ดํŠธ,๋‹คํฌ,์‹œ์Šคํ…œ 3๊ฐ€์ง€ ํ…Œ๋งˆ ๋ชจ๋“œ์™€ 4์ข…์˜ ์ปค์Šคํ†ฐ ํฐํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.


Troubleshooting

1. ์œ„์ ฏ๊ณผ ๋ฉ”์ธ ์•ฑ ๊ฐ„ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” โ€” App Group ๊ณต์œ  ModelContainer

๋ฌธ์ œ ์œ„์ ฏ์€ ๋ฉ”์ธ ์•ฑ๊ณผ ๋ณ„๋„ ํ”„๋กœ์„ธ์Šค๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฉ”์ธ ์•ฑ์˜ 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)"
    }
}

3. ์•ฑ ์žฌ์‹œ์ž‘ ์—†๋Š” ์–ธ์–ด ์ „ํ™˜ โ€” @AppStorage + .environment(.locale)

๋ฌธ์ œ ์ผ๋ฐ˜์ ์œผ๋กœ 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)
}

4. WeatherKit API ํ˜ธ์ถœ ์ตœ์ ํ™” โ€” Combine ํƒ€์ด๋จธ + ํฌ๊ทธ๋ผ์šด๋“œ ์ „์šฉ

๋ฌธ์ œ 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()
    }

5. ์ด๋ฏธ์ง€ ์ €์žฅ ์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋น„๋Œ€ํ™” ๋ฐฉ์ง€ โ€” ํŒŒ์ผ๋ช… ์ „๋žต

๋ฌธ์ œ ์ผ๊ธฐ์— ์ฒจ๋ถ€๋œ ์‚ฌ์ง„์„ 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)
}

Project Structure

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+ ํ‚ค)

License

์ด ํ”„๋กœ์ ํŠธ๋Š” ๊ฐœ์ธ ํฌํŠธํด๋ฆฌ์˜ค ํ”„๋กœ์ ํŠธ์ด๋ฉฐ, ์ƒ์—…์  ์‚ฌ์šฉ์„ ๊ธˆ์ง€ํ•ฉ๋‹ˆ๋‹ค.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages