-
๐๐ป๐๐ปโโ๏ธ๐จ ํ๋ก์ ํธ ๊ตฌ์:
24. 01. 08.~24. 01. 15. -
๐๐ป๐๐ปโโ๏ธ๐จ ํ๋ก์ ํธ ๊ธฐ๊ฐ(1์ฐจ):
24. 01. 16.~24. 03. 21.(1.0.0 ๋ฒ์ ๊ฐ๋ฐ ์๋ฃ)- [Step-1]
24. 01. 19.~24. 01. 26. - [Step-2]
24. 01. 27.~24. 02. 02. - [Step-3]
24. 02. 03.~24. 02. 16. - [Step-4]
24. 02. 22.~24. 03. 04. - [Step-5]
24. 03. 05.~24. 03. 05. - [Step-6]
24. 03. 06.~24. 03. 06. - [Step-7]
24. 03. 06.~24. 03. 07. - [Step-8]
24. 03. 07.~24. 03. 08. - [Step-9]
24. 03. 09.~24. 03. 11. - [Step-10]
24. 03. 12.~24. 03. 12. - [Step-11]
24. 03. 13.~24. 03. 15. - [Step-12]
24. 03. 16.~24. 03. 18. - [Step-13]
24. 03. 19.~24. 03. 19. - [Step-14]
24. 03. 21.~24. 03. 21.
- [Step-1]
-
๐๐ป๐๐ปโโ๏ธ๐จ ํ๋ก์ ํธ ๊ธฐ๊ฐ(2์ฐจ):
24. 04. 05.~24. 04. 06.- [Step-15]
24. 04. 05.~24. 04. 06. - [Issue-1]
24. 04. 08.~24. 04. 08.(1.0.1 - ๋ฒ๊ทธ ํฝ์ค)
- [Step-15]
-
๐๐ป๐๐ปโโ๏ธ๐จ ์ ์ง๋ณด์:
- [refactor/filter]
24. 10. 16.(1.0.2 - ๋ฒ๊ทธ ํฝ์ค) - [fix/AlertToggle]
24. 10. 17.(1.0.3 - ๋ฒ๊ทธ ํฝ์ค)
- [refactor/filter]
- ํ๋ก์ ํธ ๊ตฌ์ฑ์(iOS Developer 1์ธ)
| Lust3r |
-
์๋น์ค : ์์ธ์ ๋ฌธํ ํ๋ก๊ทธ๋จ์ ๋ํ ๋ค์ํ ์ ๋ณด ๋ฐ ํธ์ ๊ธฐ๋ฅ ์ ๊ณต
- ํ์ฌ ์ด์์ค์ธ, ์ด์ ์์ ์ธ ํ๋ก๊ทธ๋จ ์ ๋ณด ์ ๊ณต
- ํ๋ก๊ทธ๋จ ์์ธ ์ ๋ณด ์ ๊ณต
- ํ๋ก๊ทธ๋จ ๊ฒ์ ๋ฐ ํํฐ๋ง ๊ธฐ๋ฅ ์ ๊ณต
- ์บ๋ฆฐ๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ ์ง๋ณ ํ๋ก๊ทธ๋จ ๋ชฉ๋ก ์ ๊ณต
- ํ๋ก๊ทธ๋จ ์ฆ๊ฒจ์ฐพ๊ธฐ ๊ธฐ๋ฅ ๋ฐ ํธ์ ์๋ฆผ ๊ธฐ๋ฅ ์ ๊ณต
- ์ธ๋ถ ์ฑ์ ํ์ฉํ ํ๋ก๊ทธ๋จ ์์น ๊ธธ์ฐพ๊ธฐ ๊ธฐ๋ฅ ์ ๊ณต
- ์น๋ทฐ๋ฅผ ํ์ฉํ ํ๋ก๊ทธ๋จ ์๋ด ํ์ด์ง ์ฐ๊ฒฐ ๊ธฐ๋ฅ ์ ๊ณต
- ์บ๋ฆฐ๋์ ์ผ์ ์ ์ฅ ๊ธฐ๋ฅ ์ ๊ณต
-
๊ธฐ์ ์คํ : Swift
SwiftUIArchitectureModel-View Architecture
Design PatternObserver(Swift Combine)Singleton(CacheManager)
Combine(Framework)SwiftData(Framework)EventKit / EventKitUI(Framework)(iOS 17) without prompting the user access
UserNotifications(Framework)Local Push Notification
ShareLinkWKWebView(UIViewRepresentable)AsyncImageNSCacheNSCache<NSString, UIImage>
GeometryReaderNavigationNavigationStackNavigationLink
NWPathMonitorcustom sheetDeepLink: External App API UseApple MapNaver MapKakao MapKakaoTalk
DebugInstrumentsAllocation, Leak, VM TrackerXcode Memory Graph
-
ํ๋ก์ ํธ ํ๊ธฐ :
- ๊ณต๊ณต๊ธฐ๊ด ์ฌ์ง ๊ฒฝํ์ ๋ฐํ์ผ๋ก ํ์์ฑ์ ๋๊ผ๋ ์๋น์ค๋ฅผ ์ด๋ ๊ฒ ์ํ๋๋๋ก ๋ง๋ค ์ ์์ด ๋ง์กฑ์ค๋ฌ์ ๋ค.
- ๊ฐ๋ณ๊ฒ ๊ฐ์ ธ๊ฐ๋ ค ํ๋ ํ๋ก์ ํธ์์ผ๋ ์ด์ ํ๊ณ ์ถ์ ํ๋ก์ ํธ ํ๋ ๊น์ ๊ฐ๋ฐ ์ญ๋๊ณผ ๊ฒฝํ์ ๋ชจ๋ ์ฑ๊ธฐ์๋ ๋ชฉํ๋ฅผ ์ธ์ ๊ณ , ์ง์ง๋ก ์ด๊ฒ์ ๊ฒ ์ํด๋ณธ ๊ฒ๋ค๋ก๋ง ๋ค ์ฑ์๋ฃ๋ค๋ณด๋ ์ฝ ๋ ๋ฌ์ด๋ผ๋ ์๊ฐ์ด ํ๋ ๋ค.
- ๊ทธ ๋๋ถ์ ์ด์ ํ๋ก์ ํธ ๋ '๋'์ ๋นํด์ ์ง๊ธ์ '๋'๋ ์๋ ๊ฒ๋, ํ ์ ์๋ ๊ฒ๋ ๋์๊ธฐ์ ์ด '๋ฐ์ '์ด๋ผ๋ ์ธก๋ฉด์์ ๋๋ฌด ๋ง์กฑ์ค๋ฌ์ด ๊ธฐ๊ฐ์ด์๋ค.
- ์ฒ์ ๋์์ธํ๋ ํผ๊ทธ๋ง ํ์ผ์ ์์๋ก ๋ฐ๋์๊ณ , ์ด์ ๋๋ฉด ์์ธํ ์ ์๊ฒ ์ง ํ๋ ๊ธฐ์ ๋ช ์ธ์๋ ์์๋ก ๋ฐ๋์๊ธฐ์(๊ธ์ ์ ์ธ ๋ฐฉํฅ์ผ๋ก) ์ด๋์ ์ ์์ผ ๋ฐฉ๋ฒ๋ก ์ด ๋์๋ ์ถ์๋ค. ๊ฐ๋ฐํ๊ณ ํ ์คํธํ๋ค ๋ณด๋ฉด ๋ ์ข์ ๋์์ธ์ด ๋ณด์ด๊ณ , ํธ์์ฑ์ด๋ ๊ธฐ๋ฅ์ ์ธ ๋ฉด์์ ๋ ์ ๊ณตํ๊ณ ์ถ์ ๊ฒ์ด ๊ณ์์ ์ผ๋ก ๋์จ๋ค.
- ๋ ํ๋ ๋ง์กฑ์ค๋ฌ์ด ๊ฒฝํ์, ๊ทธ๋์์ ์ด์ฐจํผ ๋ญ๊ฐ๋ฅผ ๋ง๋ ๋ค๋ ๊ฒ์ ๊ทธ๋์ ์์ฃผ ํด์๋ ๊ธฐ์ด์ ์ธ ๊ฒ์ด ์๋๋ผ๋ฉด ์ฒ์์ธ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๊ทธ๊ฒ์ ํ์ตํ๊ณ ์ ์ฉํ๋ ๊ณผ์ ์ด ์์๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๊ณผ์ ์ด ๊ณง ํธ๋ฌ๋ธ ์ํ ์ด์๊ณ , ๊ธฐ๋ฅ์ ๊ตฌํํ๋ฉด์ ์ผ๋ง๋ ๋ง์ ์ง์์ ์ป์๋๋๊ฐ ์ค์ ์ด์๋ค๊ณ ์๊ฐํ๋ค. ํ์ง๋ง ์ด๋ฒ ํ๋ก์ ํธ๋ ๊ทธ๋ฐ ์ธก๋ฉด์ ๋ํด์ ์ฑ ์๋น์ค์ ๊ทผ๋ณธ์ ์ธ ์ธก๋ฉด์ ์ข ๋ ๋ณด๊ณ ์ ํ๋ ๊ฒ ๊ฐ๋ค. ๊ฐ๋ น ๋คํธ์ํฌ ์์ , ์น ์๋น์ค ์์ , ์ด๋ฏธ์ง ์์ , ์ ์ฒด ์๋น์ค ๊ณผ์ ์์ ๊ฐ์ง๋ ๋ฌธ์ ์ , ๋ฆฌ์์ค ์ ์ ์จ, ์ํคํ ์ฒ ๋ฑ. ์ด์ ํ ๋จ๊ณ ๋ ๋๊ฒ ๋ณด๋ ค ํ๋ค๋ ์ ์์ ๋๋๊ณ ๊ธฐ๋ปค์ผ๋ฉฐ ๊ทธ ๊ณผ์ ์์ ์ป์ ๋๋ฒ๊น ๊ฒฝํ์ ๋๋ฌด๋ ๊ฐ์ง๋ค๊ณ ์๊ฐํ๋ค.
- ์ด ํ๋ก์ ํธ์ ๋ํ ์ ์ ๋ ๋๊ณ , ๊ทธ ๊ณผ์ ์ด ๋๋ฌด ํ๋ณตํ๊ธฐ ๋๋ฌธ์ ์๋น์ค๊ฐ ๋๋ฆฌ ์ฌ์ฉ๋์์ผ๋ฉด ์ข๊ฒ ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ด ๋ง์กฑ์ค๋ฌ์ ์ผ๋ฉด ํ๋ค.(ํ๋ก์ ํธ๋ช ์ด '๋ผ์จ' ์ธ ์ด์ )
![]() |
![]() |
![]() |
|---|---|---|
| Onboarding_1 | Onboarding_2 | Onboarding_3 |
![]() |
![]() |
![]() |
|---|---|---|
| ProgramView(MainView) | ProgramDetailView | ProgramDetailView(Notification request) |
![]() |
![]() |
![]() |
|---|---|---|
| ProgramDetailView(ShareLink) | ProgramDetailView(Image Detail) | ProgramDetailView(Webview-homepage) |
![]() |
![]() |
![]() |
|---|---|---|
| ProgramDetailView(DeepLink-Map_1) | ProgramDetailView(DeepLink-Map_2) | ProgramDetailView(WebView-Map) |
![]() |
![]() |
![]() |
|---|---|---|
| ProgramDetailView(add to Calendar) | CalendarView | SettingsView |
![]() |
![]() |
|---|---|
| FavoritesView_1 | FavoritesView_2 |
![]() |
![]() |
![]() |
|---|---|---|
| SearchView | SearchView_Search | SearchView_Filter |
- Onboarding View ๊ตฌํ
์ํ๋ฅผ ๊ฐ์ง color ๋ณ์๋ฅผ ๋ง๋ค์ด App ์ ์ฒด์์ ์ฌ์ฉ์ํ๋ฅผ ๊ฐ์ง initial ๋ณ์๋ฅผ ๋ง๋ค์ด App ์ฒซ ์คํ ๋๋งOnboarding Page๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ์ํ๋ฅผ ๊ฐ์ง region ๋ณ์๋ฅผ ๋ง๋ค์ด ์ ํํ ์ง์ญ ์ ์ฅ ๋ฐ ํ์ฉPicker๋ฅผ ํ์ฉํ์ฌ ์ง์ญ์ ์ ํํ ์ ์๋๋ก ๊ตฌํTabView๋ฅผ ํตํดOnboarding๊ฐ ํ์ด์ง๋ฅผ ์ด๋
- Program View ๊ตฌํ
- ๊ฐ์ด๋ฐ ๋ณด์ด๋ ์ปจํ
์ธ ๊ฐ์กฐ(->
TabView๋ก ๋ณ๊ฒฝ๋์ด ์๋ ๊ฐ์กฐ) - ์์ชฝ ์ปจํ
์ธ ํฌ๋ช
๋ ์กฐ์ (->
TabView๋ก ๋ณ๊ฒฝ๋์ด ์กฐ์ ํ์ ์์) NavigationBar๊ตฌ์ฑTabView์ฌ์ฉ
- ๊ฐ์ด๋ฐ ๋ณด์ด๋ ์ปจํ
์ธ ๊ฐ์กฐ(->
- API Model ๊ตฌ์ฑ
- ๋คํธ์ํฌ ๋ถ๋ฆฌ
- ์คํ๋ผ์ธ ํ๊ฒฝ ๊ฐ์ง ๋ฐ ์๋ด
- ๋ชฉ์ , ์ค ๋ฐ์ดํฐ ํ ์คํธ
-
Search View ๊ตฌํ
ProgramView์์Search Icon์ ํฐ์นํ๋ฉดSearchView๋ก ์ด๋(NavigationLinkํ์ฉ)- ํ๋ก๊ทธ๋จ ์ ๋ชฉ์ ๊ฒ์ํ์ฌ ํํฐ๋ง
- ํํฐ๋ง์ด ๋๋ฉด ์ฒซ ๊ฒฐ๊ณผ๋ก ์์น ์ด๋
-
Filter View ๊ตฌํ
- ์นดํ
๊ณ ๋ฆฌ, ์ง์ญ์
FilterView๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฉ - ์๋์ชฝ์ sheet๋ก ์๊ฒ ๋์ ํ์ฌ context ์ ์ง
- ํํฐ ์ด๊ธฐํ, ๋ซ๊ธฐ ๋ฒํผ ๊ธฐ๋ฅ ์ ๊ณต
@State,@Binding์ ํตํ ํํฐ ์ ํ๊ฐ ๋ฐ์
- ์นดํ
๊ณ ๋ฆฌ, ์ง์ญ์
-
Program Detail View ๊ตฌํ
- ํฌ์คํฐ๋ฅผ ๋ฐฐ๊ฒฝ์ผ๋ก ์์ธํ ์ ๋ณด๋ฅผ ์คํฌ๋กคํ๋ฉฐ ๋ณผ ์ ์๋๋ก ๊ตฌํ
- ํ๋ก๊ทธ๋จ๋ช ์ฐ์ธก์ ์ฆ๊ฒจ์ฐพ๊ธฐ์ ์๋ฆผ ๋ฒํผ ์ถ๊ฐ
- ๊ด๋ จ ๋งํฌ๋ก ์ด๋ํ๋ ๋ฒํผ ์ถ๊ฐ
- ํ๋ก๊ทธ๋จ ์งํ ์ฅ์๋ฅผ ์ง๋๋ก ์ ๊ณต
-
๋ถ๊ฐ ๊ธฐ๋ฅ ๊ตฌํ
- ์ฆ๊ฒจ์ฐพ๊ธฐ ๋ฒํผ ์ฒดํฌ ์ฌ๋ถ์ ๋ฐ๋ฅธ fill ์ํ ๋ณ๊ฒฝ
- ์๋ฆผ ๋ฐ๊ธฐ ๋ฒํผ ์ฒดํฌ ์ฌ๋ถ์ ๋ฐ๋ฅธ fill ์ํ ๋ณ๊ฒฝ
- ๊ณต์ ์ํธ ๊ธฐ๋ฅ ๊ตฌํ
-
DTO
- ์ฌ์ฉํ์ง ์๋ ํญ๋ชฉ ์ ๊ฑฐ
-
ContentView
- fullScreenCover ๋์ isFirstLaunching ๊ธฐ์ค์ผ๋ก ๋ถ๊ธฐ ์ฒ๋ฆฌ
- @AppStorage ๋ฑ์ ํ๋กํผํฐ ์์น ๊ณ ๋ ค
-
ProgramView
- ์ค์ , ๊ฒ์ ์์น ๊ณ ๋ ค
- networkManager ์์น ๊ณ ๋ ค
-
ProgramCardView
- shadow, opacity ๊ฐ ์กฐ์
- ์ฌ์ฉํ์ง ์๋ ํ๋กํผํฐ ์ ๊ฑฐ
-
SearchView
- List ๋ณด์ผ๋ฌ ํ๋ ์ดํธ ์ฝ๋ ์ ๊ฑฐํ์ฌ ๋ก์ง ๊ฐ๊ฒฐํ
-
SearchCardView
- ์ด๋ฏธ์ง์ ํ ์คํธ ์์น ์กฐ์
- content๋ฅผ ์ ๋ฌํด์ ๋ด๋ถ์์ ๋์ํ๋ ๊ฒ ๊ณ ๋ ค
- ๋ด๋ถ ๋์์ ๋ํ ์บก์ํ ๋ชฉ์ + ๋ฉ๋ชจ๋ฆฌ Allocations์ 10MiB ์ฐจ์ด ๋๋ ๊ฒ ํ์ธ
-
ProgramDetailView
- ํํ์ด์ง ์ด๋ ์ ToolBar ์ ๊ณต
- .toolbar ๊ด๋ จ ์ฝ๋๊ฐ ์ค๋ณต๋์ด WebView ๋ด์ CustomWebView๋ฅผ ๊ตฌํ
- ๋ณด์ผ๋ฌ ํ๋ ์ดํธ ์ฝ๋ ์ ๊ฑฐ ๋ฐ ์ฌ์ฉํ๋ View ์ฝ๋ ๊ฐ๊ฒฐํ
- ํ์ํ WebView๋ ์ธ๋ถ์์ ์ฃผ์
- ํํ์ด์ง ์ด๋ ์ ToolBar ์ ๊ณต
- ์ด๋ฏธ์ง ์บ์ฑ
- CacheManager ๊ตฌํ
- CacheManager๋ฅผ ํ์ฉํ์ฌ ์ด๋ฏธ์ง ํํ
- WebView
- WebView ์ฌ์ฉ ์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ธ์ฆํ์ฌ ์ค์ง ์๋ ์ด์๋ฅผ ํด๊ฒฐํ์ฌ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
- Settings View ๊ตฌํ
- ์ ์๊ถ ํ์
- ์ฑ ํ ๋ง ์ค์
- ๊ด์ฌ ์ง์ญ ์ค์
- SwiftData ์ฌ์ฉ
- SwiftData์ Model ๊ตฌ์
- SwiftData๋ฅผ ํ์ฉํ์ฌ Favorites ํญ๋ชฉ ๊ด๋ฆฌ
- Favorites View ๊ตฌํ
- SwiftData์ FavoritePrograms ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ View ๊ตฌ์ฑ
- toolbar ๋ฒํผ์ ํ์ฉํ ์ ๊ฑฐ ๊ธฐ๋ฅ ๊ตฌํ
- User Notifications ๊ตฌํ
- Notification ๊ด๋ฆฌ ์ญํ ์ ๋งก์ ๊ฐ์ฒด ๊ตฌํ
- Notification ์ฌ์ฉ ๊ถํ ์์ฒญ
- User Notification์ ํ์ฉํ์ฌ ์ฆ๊ฒจ์ฐพ๊ธฐ ํ๋ก๊ทธ๋จ ์์์ผ ์๋ฆผ ๊ตฌํ
- Date์ ํ๊ตญ์๊ฐ ์ค์
- Notification ์ค๋ณต ๋ฑ๋ก ๋ฐฉ์ง
- ์์์ผ ๊ธฐ์ค ์๋ฆผ ๋ฑ๋ก ๋ถ๊ธฐ์ฒ๋ฆฌ(์ด๋ฏธ ์์ํ ํ๋ก๊ทธ๋จ์ ์๋ฆผ ๋ฑ๋ก ์ํจ)
- ์ด๋ฏธ identifier๊ฐ ๋ฑ๋ก๋์ด ์๋์ง ํ์ธ
- ์ฆ๊ฒจ์ฐพ๊ธฐ ํด์ ์ Notification ์ ๊ฑฐ ๊ธฐ๋ฅ ๊ตฌํ
- SettingsView์์ ์๋ฆผ ํ ๊ธ ๊ตฌํ ๋ฐ ์ค์ ์ฑ ์๋ฆผ ์ํ์ ์ฐ๊ฒฐ
-
WebView
- ์ฝ๋ ์ปจ๋ฒค์ ์ค์
-
SearchView
- ๋ ์ด์์ ์ฌ์กฐ์
-
ProgramDetailView
- ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง ๋์ด๋๋ ๋ฌธ์ ํด๊ฒฐ
- isFavorite ๋ก์ง ๊ฐ์
-
SettingsView
- ํ ๋ง์ ํ์ฌ ์์ ํ์
- ์ง์ญ Picker ์์ ๋ฐ์ธ๋ฉ
-
DTO
- NetworkManager์์ ๋ฐ์ดํฐ ๋งคํ ์ ํ์์คํฌํ ์ ๊ฑฐ
- SearchCardView, DescriptionView, NotificationManager์์ ํด๋น ์์ ์ฝ๋ ์ ๊ฑฐ
-
ํ๋ก์ ํธ ํด๋๋ง
- Model ํด๋ ๋ด ํ์ผ ๊ตฌ๋ถ
-
ํ๋ก์ ํธ ๊ด๋ฆฌ
- ์ ์ฒด ํ์ผ ์ฝ๋ ์ปจ๋ฒค์ ์ค์
-
NetworkManager
- API ๋ฐ์ดํฐ ์ ๊ณต์ธก ์ ๋ ฌ ์ด์๋ก ์ธํ ํธ์ถ ๋ก์ง ์์ (๊ธฐ์กด 1000๊ฐ -> ์ ์ฒด ๋ฐ์ดํฐ)
- Calendar View ๊ตฌํ
- CalendarView์ Calendar ๊ตฌํ
- Calendar์์ ๋ ์ง ์ ํ ์ ํ๋จ์ ๋ชฉ๋ก์ ๋ณด์ฌ์ฃผ๋๋ก ๊ตฌํ
- '์ค๋(Today)' ๋ฒํผ์ ํตํด ๋ค๋ฅธ ๋ ์ง์ผ ๋ ์ค๋ ๋ ์ง๋ก ๋ฐ๋ก ๋์๊ฐ๋ ๊ธฐ๋ฅ ๊ตฌํ
- ํญ๋ชฉ์ ํฐ์นํ๋ฉด ์์ธ ํ์ด์ง๋ก ๋์ด๊ฐ๋๋ก ๊ตฌํ
- ํญ๋ชฉ ์ข์ธก์ ์ฆ๊ฒจ์ฐพ๊ธฐ ์ฌ๋ถ ํ๊ธฐ
- EventKit์ ํ์ฉํ์ฌ ProgramDetailView์ ์์์ผ, ์ข ๋ฃ์ผ ์์ ์บ๋ฆฐ๋์ ๋ฑ๋กํ๊ธฐ ๊ธฐ๋ฅ ๊ตฌํ
-
๋ถ๊ฐ ๊ธฐ๋ฅ
- ์นด์นด์คํก ํ๋ฌ์ค์น๊ตฌ ์ฐ๊ฒฐ๋ก ๋ฌธ์ ์ฐฝ๊ตฌ ๊ฐ์ค
- ์ฑ ๋ฒ์ ํ์ธ ๊ธฐ๋ฅ ๊ตฌํ
- ์ฑ ์์ด์ฝ ์ ์ฉ
- ์ฑ ์ค์น ์ด๋ฆ, ํ๊ฒ ๋ณ๊ฒฝ
- (ProgramDetailView) ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง ํด๋ฆญํ๋ฉด ์๋ณธ ์ด๋ฏธ์ง ํํ ๊ธฐ๋ฅ ์ ๊ณต
-
ํ๋ก์ ํธ ๊ฐ์
- (SettingsView) ์ ํธ์ง์ญ ํ ์คํธ ์์์ด ๋คํฌ๋ชจ๋์์ ๋ณ๊ฒฝ๋๋ ํ์ ํด๊ฒฐ
- (SettingsView) ์๋ฆผ ํ ๊ธ Alert ๋ซ๊ธฐ ๋ก์ง ๊ฐ์
- (CopyRightView) ์ด์ฉ์ฝ๊ด ๋งํฌ๋ฅผ ์ฌ๋ฆฌ๋ ๊ฒ์ด ์๋๋ผ ์น๋ทฐ๋ก ์ฐ๊ฒฐ
- (FilterView) ๋ซ๊ธฐ ๋ฒํผ์ ๋คํฌ๋ชจ๋ ์์ธ์ฑ ๊ฐ์
- (ProgramDetailView) ์ฆ๊ฒจ์ฐพ๊ธฐ, ๊ณต์ ํ๊ธฐ ๋ฒํผ ์์ ์์ธ์ฑ ๊ฐ์
- (OnboardingView) ํ๋ก๊ทธ๋จ ์ด๋ฆ ๋ณ๊ฒฝ
- (ProgramView) ์๋ก๊ณ ์นจ ์ ๋ฐ์ดํฐ๊ฐ ๋์ค์ง ์๋ ๋ฌธ์ ํด๊ฒฐ
- TestFlight ํตํ ์ด์ฉ์ ํผ๋๋ฐฑ ๋ฐ App Store ์ฌ์ฌ ๋ด์ฉ ๋ณด์
-
Onboarding View
- ์ฌ์ฉ๋ฒ ์ถ๊ฐ ์๋ด
- ์จ๋ณด๋ฉ ํ๋ฉด์์ ์ฐ์ธก์ผ๋ก pan gesture์ ์ข์ธก์ ํฐ ํ๋ฉด์ด ๋์ค๋ ํ์ ํด๊ฒฐ
-
Calendar View
- ์ฆ๊ฒจ์ฐพ๊ธฐํ ํญ๋ชฉ์ ๋ฆฌ์คํธ ์๋จ์ผ๋ก ์ฌ๋ผ๊ฐ๋๋ก ๊ตฌํ
-
Program View
- ์๋ก๊ณ ์นจ ์ ์ธ๋์ผ์ดํฐ ํ์๋๋๋ก ๊ตฌํ
-
Favorites View
- ํธ์ง ๋ชจ๋ ์ ProgramDetailView ์ด๋์ด ์๋๋๋ก ๊ตฌํ
-
ProgramDetail View
- ์์น ํ ์คํธ๋ฅผ ๋ฒํผ์ผ๋ก ๋ณ๊ฒฝํ์ฌ ๋๋ฅด๋ฉด ์ก์ ์ํธ๋ก ํญ๋ชฉ์ ์ ํํ ์ ์๋๋ก ๋ณ๊ฒฝ
- ์ ํ ์ง๋ ์ฐ๊ฒฐ
- ๋ฅ๋งํฌ ์ฐ๊ฒฐ ์ ๊ธธ์ฐพ๊ธฐ๊ฐ ์๋๋ผ ์์น ์ ๋ณด๋ฅผ ์ ๊ณต
- DescriptionView ์๋จ ์ฌ๋ฐฑ ์ถ๊ฐ
-
Feature
- ํญ๋ฐ๋ฅผ ํฐ์นํ์ฌ ์ฒ์ ํ๋ฉด์ผ๋ก ๊ฐ๋๋ก ๊ธฐ๋ฅ ๊ตฌํ(Tap to Root)
- Issue-1: NavigationTitle's wrong position
- ์ค์ ํ๋ฉด์์ ์ฑ ์์ ํน์ ์ง์ญ ์ ํ ์ ํ๋ก๊ทธ๋จ ํญ ๋ด๋น๊ฒ์ด์ ์ ๋ชฉ์ด ํ๋จ์ผ๋ก ๋ด๋ ค์ค๋ ํ์ ํด๊ฒฐ
- refactor/filter: ํํฐ ํฌ๋์ ์ด์ ํด๊ฒฐ
- ๊ฒ์ ํ๋ฉด์์ ์นดํ ๊ณ ๋ฆฌ ํน์ ์ง์ญ์ ์ ํํ์ ๋ ํด๋นํ๋ ํ๋ก๊ทธ๋จ์ด ์๋ค๋ฉด ์ฑ์ด ์ถฉ๋๋๋ ํ์ ํด๊ฒฐ
- fix/AlertToggle: ์๋ฆผ ํ ๊ธ ๋น์ ์์ ๋์ ํด๊ฒฐ
- ์ค์ ํ๋ฉด์์ ์๋ฆผ ํ ๊ธ์ด ์ฑ ์๋ฆผ ๊ถํ๊ณผ ๋๊ธฐํ๋์ง ์๊ณ ์์ฒด์ ์ผ๋ก On/Off ํ ๊ธ๋๋ ํ์ ํด๊ฒฐ
- ์ด๊ธฐ ์๋์ ๊ฐ์ด ์ฑ ์๋ฆผ ๊ถํ๊ณผ ๋๊ธฐํ ๋ฐ ๊ธฐ๋ณธ ์ค์ ์ฑ์ผ๋ก ์ฐ๊ฒฐ๋๋ Alert ํํ
๊ณ ๋ฏผํ ์ :
- Onboarding Page๋ฅผ ๋ง๋๋ ๋ฐ ์์ด์, View๋ ๊ฐ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ณ ๋ ์ด์์์ ์ก์ผ๋ฉด ๋์ง๋ง, ๊ทธ ๊ณผ์ ์์ ์ฌ์ฉํ ๋ฐ์ดํฐ, ์ ์ฅํ ๋ฐ์ดํฐ๋ ์ด๋ป๊ฒ ๊ด๋ฆฌํ๊ณ ์ด๋ป๊ฒ ๋ถ๋ฆฌํ๋ฉด ์ข์์ง ๊ณ ๋ฏผํจ
๊ณผ์ ๋ฐ ํด๊ฒฐ :
-
WWDC20 Data Essentials in SwiftUI์์์ด ๋ง์ด ์ฐธ๊ณ ๊ฐ ๋์๋ค. ํด๋น ์์์์, ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ ๋ ๊ณ ๋ คํด์ผ ํ 3๊ฐ์ง ์ง๋ฌธ์ ์๋ ค์คฌ๋๋ฐ, ๋ค์๊ณผ ๊ฐ๋ค.1) View๊ฐ ์์ ์ ์ํํ๋ ค๋ฉด ์ด๋ค ๋ฐ์ดํฐ๊ฐ ํ์ํ๊ฐ? 2) View๊ฐ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์กฐ์ํ๋๊ฐ? 3) ๋ฐ์ดํฐ๋ ์ด๋์์ ์ค๋๊ฐ?(Source of Truth)
-
์ด์ธ์๋
property wrapper์ ์ฌ์ฉ ํ๊ฒฝ์ ๋ํ ์ค๋ช ๋์ ๊ณ ๋ฏผ์ ํด๊ฒฐํ ์ ์์๋ค. ๊ฐ๋ น Step 1์ ์ํ๋ฅผ ๊ฐ์ง ๋ฐ์ดํฐ๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณด์๋ค.- Color
- ์ฑ์ ๋ฉ์ธ ์์์ด ๋ฌด์์ธ์ง ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๋ฐ์ดํฐ๊ฐ ํ์
- ์ค์ ์์ ๋ฉ์ธ ์์์ ๋ณ๊ฒฝํ ์ ์๊ฒ ์ ๊ทผํ ์ ์์ด์ผ ํจ. ๋๋จธ์ง View๋ ๋จ์ ๋ฐ์ดํฐ ์ฝ๊ธฐ
- App ๋ด์์ ๊ธ๋ก๋ฒํ๊ฒ ์ฌ์ฉ๋๊ณ , ์์ฃผ ๋ฐ๋์ง ์๋ ์์ ์ค์ ๊ฐ์ด๊ธฐ์
AppStorage๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฅํ๊ณbinding์ ํตํด ํ์ฉ
- Initial
- App์ ์ค์นํ ์ดํ ์ฒ์ ์คํํ ๊ฒ์ธ์ง ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๋ฐ์ดํฐ๊ฐ ํ์
- ContentView์์๋ OnboardingTabView๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ฌ๋ถ์ ์ฌ์ฉํ๊ธฐ ์ํด ๋จ์ํ ๊ฐ์ ์ฝ๋ ๊ฒ๋ง ์ํ, OnboardingPage์ ๋ง์ง๋ง์ธ ThirdView์์๋ ํด๋น ๊ฐ์ ๋ณ๊ฒฝํ๊ธฐ ์ํ ์ ๊ทผ์ด ํ์
- ์ด ๋ฐ์ดํฐ๋ App ์ต์ด ์คํ ์ดํ์๋ ๋ณ๊ฒฝ๋ ์ผ์ด ์์ด
State๋Observable๋ก ์ถ์ ํ ํ์๊ฐ ์๊ณ , ์์ Bool ๋ฐ์ดํฐ์ด๊ธฐ ๋๋ฌธ์AppStorage๋ฅผ ์ฌ์ฉํ๊ณ ,binding์ ํตํด ํ์ฉ
- Region
- ์ฌ์ฉ์๊ฐ ์ ํํ ์ง์ญ์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๋ฐ์ดํฐ๊ฐ ํ์
- OnboardingSecondView์ ์ค์ ์์ ๊ฐ์ ๋ฐ๊พธ๊ธฐ ์ํ ์ ๊ทผ์ด ํ์, ๋ฉ์ธ ๋ก์ง์์๋ ํด๋น ๊ฐ์ ๋จ์ํ ์ฐธ๊ณ ํ์ฌ ์์ ์ํ
- ์ฒ์์๋ ์ถ์ ์ ์ํด
ObservableObject๋ฅผ ๊ณ ๋ คํ์ผ๋, ์ด ์ญ์ ํฌ๊ธฐ๊ฐ ์์ ์ค์ ๊ฐ์ด๊ธฐ ๋๋ฌธ์AppStorage๋ฅผ ์ฌ์ฉํ๊ณ ์์ ๋ทฐ์์ ํ์ ๋ทฐ๋กbinding์ ํตํด ํ์ฉ
๊ณ ๋ฏผํ ์ :
- Initial์ Bool๋ก ๊ฐ์ ์ ์ฅํ๋ฉด ๋๋๋ฐ, Color, Region์ Enumํ์ ์ผ๋ก ๋ง๋ค์๊ธฐ์ String์ผ๋ก ์ ์ฅํ๊ณ ๊ทธ๊ฑธ ๋ถ๋ฌ์์ ๋งค์นํ๋ ๋ฐฉ๋ฒ์ด ๋ง๋ ๊ณ ๋ฏผํจ
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- Enum์
rawPresentable์ ์ค์ํ๊ณ ,@AppStorage๋ ๊ทธ ํ์ ์ ์ง์ํ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฅํ๋ค. Color๋ ๊ฐ case๊ฐ asset์ ์ด๋ฆ์ rawValue๋ก ๊ฐ๊ณ ์๊ณ , Region๋ ๊ฐ ์ผ์ด์ค๋ช ์ ๊ทธ๋๋ก rawValue๋ก ๊ฐ๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ณ ๋ฏผํ ์ :
- API ์์ฒญ์ด http๋ก ์ ํ๋์ด ์๋๋ฐ ๋ณด์์ ๋ฌธ์ ๋ก https๋ฅผ ์จ์ผ ํ๋ ์ํฉ(since it does not conform to ATS policy ์ค๋ฅ ๋ฐ์)
๊ณผ์ ๋ฐ ํด๊ฒฐ :
Info.plistํ์ผ ์กฐ์ ์ ํตํด ํด๊ฒฐ์ด ๊ฐ๋ฅApp Transport Security Settings Key์ฌ์ฉException Domains์ ์ฌ์ฉํ ๋๋ฉ์ธ ๊ธฐ์- ๋ง์ฝ http์ธ ์ ์ฒด ๋๋ฉ์ธ์ ํ์ฉํ๋ ๊ฒฝ์ฐ์๋
NSExceptionDomains๋์NSAllowsArbitraryLoads์ฌ์ฉ
๊ณ ๋ฏผํ ์ :
- DTO๋ฅผ API ๋ฐ์ดํฐ ํ์ ์ ๋ง๊ฒ ๊ตฌ์ฑํ๋๋ฐ ๋์ฝ๋ฉ ์ค๋ฅ ๋ฐ์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
CodingKeys๋งค์นญ์ ๋ฌธ์ ์์. ์ผ๋ถ CodingKeys๊ฐ Codingkeys๋ก ์ค๊ธฐ์ ๋์ด์์๋ค.
๊ณ ๋ฏผํ ์ :
- URLSession์
dataTaskPublisher๋ฅผ ์ฌ์ฉํ์ฌ API ํธ์ถ์ ํ๋๋ฐ, ์ฒ์ ํ ๋ฒ๋ง ๋ถ๋ฅด๋ฉด ์๊ด์ด ์์ผ๋ ์๋ก๊ณ ์นจ ํน์ ์ค๋ฅ๋ก ์ธํด ์ค๋ณต์ผ๋ก ํธ์ถ์ด ๋๋ฉด ๋ฆฌ์์ค ๋ญ๋น๊ฐ ์์ ๊ฒ์ผ๋ก ํ๋จ๋์ด ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผ
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ํด๋น Publisher๋
AnyCancellableํ์ ์ด๊ธฐ์ ํด๋น ๋น๋๊ธฐ ์์ ์ ์ํํ ๋ NetworkManager์ programCancellable ๋ณ์์ ์ ์ฅ - API ์์ฒญ ๋ฉ์๋๊ฐ ํธ์ถ๋๋ฉด programCancellable์
cancel()ํ๊ณ ์๋กdataTaskPublisher๋ฅผ ๋ง๋ค์ด ๋น๋๊ธฐ ์์ ์ํ - ์ค๋ณต ํธ์ถ์ด ์๋๋ผ๋ฉด ์์ ์๋ฃ ํ ํ ๋น ํด์
๊ณ ๋ฏผํ ์ :
AsyncImage์์ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ค๋ ๋์ ๋คํธ์ํฌ๊ฐ ๋๊ธฐ๊ฒ ๋๋ฉด placeholder๋ก ์ง์ ํ ProgressView๋ง ๋ณด์ด๊ฒ ๋จ- ๋คํธ์ํฌ๊ฐ ๋ค์ ์ฐ๊ฒฐ๋๋ฉด ๋ณด์ด๋๋ก ํ๊ณ ์ถ์๋ฐ ๋ค๋ฅธ TabView๋ก ํ์ด์งํ๋ฉด ํด๋น ๊ฐ์ฒด๋ ๋ค์ ๊ทธ๋ ค์ง๊ธฐ ๋๋ฌธ์ ์ด๋ฏธ์ง๊ฐ ๋ณด์ด๋ ์ด๋ฏธ ๋ก๋ฉ์ค๋ฅ๊ฐ ๋
AsyncImage๋ ์ด๋ฏธ์ง๊ฐ ๋ณด์ด์ง ์์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- API ์์ฒญ ๋ฉ์๋๋ฅผ ํธ์ถํ์ผ๋ URL์ด ๋์ผํ๊ธฐ ๋๋ฌธ์ ์๋ก ๊ทธ๋ ค์ง์ง ์์์ ํ์ธ
- NetworkManager์์ ํด๋น ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ ๊ธฐ์กด contents๋ฅผ ๋น์ฐ๋ ์์ ์ ์ถ๊ฐํ์ฌ ๋ณํ๋ฅผ ์ค์ผ๋ก์จ ํด๊ฒฐํ ์ ์์์
๊ณ ๋ฏผํ ์ :
ProgramView์์ ์๋ก๊ณ ์นจ ๋ฒํผ์ ํตํด ์์ดํ ์ ์๋ก ๋ฐ์์์ ๋,TabView Page์ ๋งจ ์ฒ์์ผ๋ก ๋์๊ฐ๊ฒ ํ๊ณ ์ถ์ผ๋ ๋ค์ํ ๋ฐฉ๋ฒ์ ์๋ํด๋ ์ ์ฉ๋์ง ์์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
@Stateํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ์๋ค.TabView์selectionํ๋ผ๋ฏธํฐ๋@Stateํ๋กํผํฐ์ ๊ฐTabViewItem์tag์กฐํฉ์ผ๋ก๋ง ์จ์ผํ๋ ์ค ์์๋๋ฐ, ์ด๋ ๊ฐ ํ์ด์ง๊ฐ ๋ฒํผ์ ํตํด ์์ง์ด๊ฑฐ๋ ํ ๋ ์ฌ์ฉํ ์ ์๊ณ , ์ด ๋ฌธ์ ์ ๊ฒฝ์ฐ์๋ ๋จ์ํ ์ฒซ ํ์ด์ง๋ก๋ง ์ด๋ํ๋ฉด ๋๋ ๊ฒ์ด๋ค.@Stateํ๋กํผํฐ๋ก ์์์ ๋ฌธ์์ด ํน์ ์ ์๋ฅผ ์์ฑํ๊ณ ,selectionํ๋ผ๋ฏธํฐ์ ํด๋น ํ๋กํผํฐ๋ฅผ ์ ๋ฌํ ํ, ์๋ก๊ณ ์นจ์ ํ ๋@Stateํ๋กํผํฐ์ ๊ฐ์ ์ฒ์์ ์ง์ ํ ๊ฐ์ผ๋ก ๋ณ๊ฒฝํด์ฃผ๋ ์ฝ๋๋ฅผ ์ํํ๋๋ก ํ๋ค.TabView์์selection์ ์ฌ์ฉํ๋ฉด ์ฒซ ์์ดํ ์selection์ ๋ค์ด์จ@State์ ๊ฐ์ผ๋ก ์ผ๊ธฐ ๋๋ฌธ์ ์ด ๋ฐฉ๋ฒ์ ํตํด ๋์๊ฐ ์ ์์๋ค.
๊ณ ๋ฏผํ ์ :
searchable๋ก ๊ฒ์๊ธฐ๋ฅ์ ๊ตฌํํ๋๋ฐ, ์ผ๋ฐ์ ์ผ๋ก ์ฑ์ ์ฌ์ฉํ ๋์ฒ๋ผ ํ๋ฉด์ ํฐ์นํ์ ๋ ํค๋ณด๋๋ฅผ ๋ด๋ฆฌ๊ณ ์ ํ์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
View+Extension์hideKeyboard()๋ฉ์๋๋ฅผ ๊ตฌํํ์ฌ ํด๊ฒฐUIApplication์ธ์คํด์ค๋์ฑ๊ธํค๊ฐ์ฒด์ธ๋ฐ,์ฌ์ฉ์ ์ด๋ฒคํธ์ ๋ผ์ฐํ ์ฒ๋ฆฌ,์ ์ด ๊ฐ์ฒด๊ฐ ์ ๋ฌํ ๋์ ๋ฉ์์ง๋ฅผ ์ ์ ํ๊ฒ ์ ์กํ๋ ๋ฑ์ ์ญํ ์ ํ๋๋ฐ, ์ด๋ฅผ ํ์ฉํ ์ ์์๋ค.UIApplication.shared๋ก ์ฑ๊ธํค ๊ฐ์ฒด์ ์ ๊ทผํ๊ณ ,sendAction๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌUIResponder์resignFirstResponder๋ฅผ ์ํํ๋๋ก ํ๋ค.
๊ณ ๋ฏผํ ์ :
SearchView์์ ์คํฌ๋กค์ ๋ด๋ฆฐ ์ํ๋ก ํํฐ๋ฅผ ์ ์ฉํ๋ฉด ๊ทธ ์์น์์ ํญ๋ชฉ์ด ๋ณ๊ฒฝ๋๊ธฐ ๋๋ฌธ์ ๋งจ ์๋ก ๋ค์ ์ฌ๋ ค์ผ ํ๋ ๋ถํธํจ์ด ์์ด ์ด๋ฅผ ํด๊ฒฐํ๊ณ ์ ํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
TabView๋์ฒ๋ผ ์ฒซ ํญ๋ชฉ์ ์ง์ ํ ์ ์๊ธฐ์ScrollViewReader๋กView๋ฅผembedํ๊ณ ,proxy๋ฅผ ์ฌ์ฉํ์ฌscrollTo๋ฉ์๋๋ฅผ ์ฌ์ฉํ์๋ค.- ๋ค๋ง ๋ณ๊ฒฝ๋๋ ํญ๋ชฉ ์์์ ์ด๋ป๊ฒ ๋งจ ์์ ๊ฐ์ ์ป๋๋๊ฐ ๊ด๊ฑด์ด์๋๋ฐ, ํํฐ ๊ฒฐ๊ณผ์ ์๊ด์์ด ๊ณ ์ ์ ์ผ๋ก ์๋จ์ ์กด์ฌํ๋
EmptyView๋ฅผ ๋ง๋ค๊ณ ํด๋นView์id๋ฅผ ๋ถ์ฌํ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์์๋ค.
๊ณ ๋ฏผํ ์ :
UIKit์์๋WebViewํน์SFSafariViewController๋ฅผ ๊ทธ๋ฅ ์ฌ์ฉํ๋ฉด ๋์์ง๋งSwiftUI์์ ์น๋ทฐ ๊ธฐ๋ฅ์ ๋ค์ดํฐ๋ธ๋ก ์ ๊ณตํ์ง ์์UIKit์ ๋ฐฉ์์ ์จ์ผ ํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
UIKit์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๊ฒ ํด์ฃผ๋UIViewRepresentableํ๋กํ ์ฝ์ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ ์ ์์๋ค.makeUIView,updateUIView๋ ๊ฐ์ ํ์ ๋ฉ์๋๋ฅผ ๊ตฌํํด์ผ ํ๋๋ฐ, ์ ์๋ View๋ฅผ ๋ง๋ค๊ณ ์ด๊ธฐ ์ค์ ์ ์งํํ๋ ๋ฉ์๋์ด๊ณ ํ์๋ ์๋ก์ด ์ ๋ณด๋ก View๋ฅผ ์ ๋ฐ์ดํธ ํด์ฃผ๋ ๋ฉ์๋์ด๋ค.makeUIView์์ ์ด๋ค View ํ์ ์ ๋ฐํํ ๊ฒ์ธ์ง ๋ช ์ํด์ฃผ๊ณ ,updateUIView์์์ ํ๋ผ๋ฏธํฐ ํ์ ๋ ์ด์ ๋ง์ถฐ์ ๋ณ๊ฒฝํด์ค์ผ ํ๋ค.makeUIView์์ ๋นWKWebView๋ฅผ ๋ฐํํ๊ณ ,updateUIView์์ ์ฃผ์ด์ง url์ Request๋ก ๋ง๋ค์ดWKWebView์์ ๋ถ๋ฌ์ค๋๋ก ํ๋ค.
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
// MARK: - Public Properties
let urlToConnect: String
// MARK: - Private Properties
private var webView: WKWebView
// MARK: - Initializer
init(urlToConnect: String) {
self.urlToConnect = urlToConnect
self.webView = WKWebView()
}
// MARK: - UIViewRepresentable
func makeUIView(context: Context) -> WKWebView {
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
guard let url = URL(string: urlToConnect) else { return }
let urlRequest = URLRequest(url: url)
webView.load(urlRequest)
}
// MARK: - Public Functions
func goBack() {
webView.goBack()
}
func goForward() {
webView.goForward()
}
func reload() {
webView.reload()
}
}๊ณ ๋ฏผํ ์ :
- ์์ธ ํ์ด์ง์ ๋ค์ด๊ฐ์ ๋
TabBar์ ์์์ด ํ์ด์ง ๋ฐฐ๊ฒฝ ์์๊ณผ ์ผ์นํ์ผ๋ฉด ์ข๊ฒ ์ผ๋ ์์ ๋ณ๊ฒฝ ์ฝ๋๊ฐ ์ ์ฉ๋์ง ์์๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
init()์์UITabBar.appearance().backgroundColor์์ฑ์ ํตํด ๋ณ๊ฒฝํ ์ ์์๋ค.
init() {
UITabBar.appearance().backgroundColor = UIColor.systemBackground
}๊ณ ๋ฏผํ ์ :
- ํ๋ก๊ทธ๋จ์ด ์งํ๋๋ ์ฅ์๋ฅผ ์ง๋๋ฅผ ํตํด ๋ณด์ฌ์ฃผ๊ณ ์ถ์ผ๋
MapKit์ ์ฌ์ฉํ๋ฉด ์ฑ์ UI๊ฐ ๊นจ์ง๋ ํ์์ด ๋ฐ์ํ๋ค(๋ด๋น๊ฒ์ด์ ๋ฐ, ํญ๋ฐ ์์ ๋ณ๊ฒฝ ๋ฐ ๋ด๋น๊ฒ์ด์ ํ์ดํ ์ ์ด๊ธฐํ).
๊ณผ์ ๋ฐ ํด๊ฒฐ :
ProgramDetailView์์DescriptionView์Map์ ๋ด์๋ณด๋ ๋ฐฉ๋ฒ, MapView๋ฅผ ๋ฐ๋ก ๊ตฌํํ๊ณ ์ธ์คํด์ค๋ก ๋ถ๋ฌ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ, ๋ด๋น๊ฒ์ด์ ๋ฐ์ ํญ๋ฐ ์์์ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ ๋ฑ ๋ค๋ฐฉ๋ฉด์ผ๋ก ์๋ํ์์ผ๋ ์ํ๋๋๋ก ์ด๋ค์ง์ง ์์๋ค.- ์ ํ ๊ธฐ๋ณธ ์ง๋ ์ฑ์ผ๋ก ์ฐ๊ฒฐ์ํค๊ณ ์ ํ์ผ๋ ๊ธฐ๋ณธ ์ง๋ ์ฑ์์ ์๊ตฌํ๋ ์ ๋ณด์ ๋ฐ์ดํฐ์์ ์ ๊ณตํ๋ ์ ๋ณด๊ฐ ๋ง์ง ์์ ์์น ์ ๋ณด๋ฅผ ํ์ํ ์ ์๋ ๋ฐ์ดํฐ๊ฐ ์์๋ค.
- ์ฌ๋๋ค์ด ๋ณดํต ๋ง์ด ์ฌ์ฉํ๋ ์ธ๋ถ ์ง๋ ์ฑ(๋ค์ด๋ฒ, ์นด์นด์ค)์ ์ฌ์ฉํ์ฌ ์์น ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ณ ์ ํ๋ค.
- ๊ทธ๋ฌ๋ ์ฑ ๋ด์์ ์กฐ์์ ํ๊ธฐ๋ณด๋ค ์ค์ ์ฑ์์ ๊ฒฝ๋ก๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ๋ ํจ์จ์ ์ด๋ผ ํ๋จํ์ฌ ๊ฐ ์ฑ์ API๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก๊ทธ๋จ์ด ์งํ๋๋ ์ฅ์์ ์, ๊ฒฝ๋๋ฅผ ์ ๋ฌํ๊ณ ํ์ฌ ์์น์์ ๊ฐ๋ ๊ฒฝ๋ก๋ฅผ ๋์ค๊ตํต์ผ๋ก ๋ณด์ฌ์ฃผ์๋ค.
- ์ฑ์ด ์ค์น๋์ด ์์ผ๋ฉด ์คํํ์ฌ ๊ฒฝ๋ก๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ์๋ค๋ฉด ์ฑ์คํ ์ด์ ์ฐ๊ฒฐํ์ง๋ง ์ธ๋ถ ์ฑ์ ์ฌ์ฉํ๊ณ ์ถ์ง ์์ ์ด์ฉ์๋ ์์ ๊ฒ์ด๊ธฐ์
WebView๋ฅผ ํตํด ํฌํธ์ฌ์ดํธ ์ฟผ๋ฆฌ์ ํด๋น ์ฅ์๋ช ์ ์ง์ด๋ฃ์ด ์ฑ ๋ด์์๋ ์ฅ์ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๊ฒ ํ๋ค.
struct MapButton: View {
// MARK: - Public Properties
var mapType: MapType
var latitude: String
var longitude: String
// MARK: - Body
var body: some View {
switch mapType {
case .naver:
Button(action: {
LinkToNaverMap(
latitude: Double(latitude) ?? 0,
longitude: Double(longitude) ?? 0)
}, label: {
Image("Naver_map_Icon")
.resizable()
.frame(width: 30, height: 30)
})
case .kakao:
Button(action: {
LinkToKakaoMap(
latitude: Double(latitude) ?? 0,
longitude: Double(longitude) ?? 0)
}, label: {
Image("Kakao_map_Icon")
.resizable()
.frame(width: 30, height: 30)
})
}
}
}๊ณ ๋ฏผํ ์ :
- ๋งค๋ฒ
AsyncImage๋ก ๋ถ๋ฌ์ค๋ฉด ๋ฐ์ดํฐ ๋ญ๋น๊ฐ ์ผ์ด๋๊ณ ,List๋๋TabView์์lazy loading์ ํ๋ฉด์ ์ข ์ข ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.Caching์ ํตํด ์ ์ฅ๋ ์ด๋ฏธ์ง๊ฐ ์๋ค๋ฉด ๋ถ๋ฌ์ค๊ณ , ์๋ค๋ฉด ๊ทธ๊ฒ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฐพ๊ณ ์ ํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ๊ธฐ์กด์ ์ฌ์ฉํ
AsyncImage๋ฅผ ์ต๋ํ ํ์ฉํด๋ณด๋ ค ํ์ผ๋Imageํ์ ์UIImage๋ก๋ ๋ณํํ ์ ์์ด(๋ฐ๋๋ ๊ฐ๋ฅํ์ง๋ง) ์๋กญ๊ฒ ์ด๋ฏธ์ง๋ฅผ ํํํ ํ์๊ฐ ์์๋ค. - ์ฐ์
CacheManager๋ฅผ์ฑ๊ธํค ํจํด์ผ๋ก ๊ตฌํํ๊ณ , ๊ทธ ์์ ์๋private cache๋ฅผ ํ์ฉํ๊ธฐ๋ก ํ๋ค. CacheManager์fetchImage(urlString: completion:)๋ฉ์๋์content์imageURL์ ๋ณด๋ด ํธ์ถํ๋ฉด,cache์ ํด๋นstring์NSString์ผ๋ก ๋ณํํ ๊ฐ์ ์ด๋ฏธ์ง๊ฐ ์๋์ง ํ์ธ์ ํ๊ณ , ์๋ค๋ฉด ์ด๋ฏธ์ง๋ฅผcompletion์ผ๋ก ๋ฐํ, ์๋ค๋ฉดURLSession์ ํตํด ๋ฐ์ ํ ์บ์์ ์ ์ฅํ๊ณ ๋ฐํํ๋ ๋ฐฉ์์ ์ฌ์ฉ- ์ด๋ฌํ ์ผ๋ จ์ ๋น๋๊ธฐ ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ์ํด
@escaping closure๋ฅผ ํ์ฉ - ์ดํ ์ปค์คํ
ํ View์ธ
CachedAsyncImageView์์CacheManager์fetchImage๋ฅผ ํ์ฉ, ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ์๋ค.
final class CacheManager {
// MARK: - Singleton Pattern
static let shared = CacheManager()
// MARK: - Initializer
private init() { }
// MARK: - Private Properties
private var cache = NSCache<NSString, UIImage>()
// MARK: - Public Functions
func fetchImage(urlString: String, completion: @escaping (UIImage?) -> Void) {
if let cachedImage = cache.object(forKey: urlString as NSString) {
completion(cachedImage)
} else {
guard let url = URL(string: urlString) else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data, let image = UIImage(data: data) else {
completion(nil)
return
}
self.cache.setObject(image, forKey: urlString as NSString)
completion(image)
}.resume()
}
}
}![]() |
![]() |
|---|---|
| ์บ์ฑ ์ ๋ฉ๋ชจ๋ฆฌ | ์บ์ฑ ์ ๋คํธ์ํฌ |
![]() |
![]() |
|---|---|
| ์บ์ฑ ํ ๋ฉ๋ชจ๋ฆฌ | ์บ์ฑ ํ ๋คํธ์ํฌ |
- ๊ทธ๋ํ์ ๊ฐ์ด ์บ์ฑ ์ ์๋ ๋งค ํ์ด์ง(์คํฌ๋กค) ๋๋ง๋ค ์๋กญ๊ฒ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ค์ญ๋ ์ญํ์ง๋ง, ์บ์ฑ ํ์๋ ๋ฉ๋ชจ๋ฆฌ ์์ฒด ์ฌ์ฉ๋์ ๋์ผ๋ ํ์ด์ง(์คํฌ๋กค)์์ ์ด๋ฏธ ๋ถ๋ฌ์จ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ดํ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์๋กญ๊ฒ ๋ถ๋ฌ์ค์ง ์๋ ์ด์ ๊ณ ์ ๋จ์ ๋ณผ ์ ์๋ค.
๊ณ ๋ฏผํ ์ :
WKWebView๋ฅผUIViewRepresentable์ ์ฌ์ฉํ์ฌSwiftUI View๋ก ์ฌ์ฉํ๋๋ฐ,Instruments์Leak์ ์ฒดํฌํ๋ฉฐ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋น์ ์์ ์ผ๋ก ๋์์ง๋ ํ์์ ๋ฐ๊ฒฌํ์๊ณ , ์ด๋ฅผ ํด๊ฒฐํ๊ณ ์ ํ๋ค.Instruments์ธก์ ๋ฐ์ดํฐ์Leak์ ๋ฐ์ํ์ง ์์ง๋งAllocation์์WebView๋ฅผ ์คํํ๋ ์๊ฐ ๋ฉ๋ชจ๋ฆฌ๊ฐ 4~50MiB์์ 550MiB๋ก ๊ธ์ฆ, ์ฝ 500MiB์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ ๋จ์ผVM Allocation์์ ํ์ธํ๋ค.VM Allocation์ ๋ณด์ ๋ฐ๋ฅด๋ฉดWKWebView์์ ๋ฐ์ํ ํ ๋น์ด๋ฉฐ,Responsible Library๋JavaScriptCore์๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
WKWebView์ ์ฌ์ฉ์ ๋ฌธ์ ์ธ๊ฐ?
UIViewRepresentable๊ตฌํ์ ๋ฌธ์ ๋ ์์๊ณ , ๋ณดํตWKWebView๋ฅผ ์ฌ์ฉํ๋ฉด์ ํํ ๋ฐ์ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ด์๋ ์ปค์คํ ์ฝ๋๋ค์ดํฐ๋ฅผ ๋ง๋ค๋ฉด์self๋ฅผ ๋๊ฒจ์ฃผ๋ ๊ณผ์ ์์ ๋ฐ์ํ๋ ๊ฐํ ์ฐธ์กฐ์๊ธฐ์ ์ด ์ผ์ด์ค์ ๋ฐ์ํ ์ ์์๋ค.
- ์ง์ ํ ๋น ํด์ ๋ฅผ ํด์ค์ผ ํ๋๊ฐ?
UIViewRepresentable๋ก ๊ตฌํํWebView๋ ๊ฐํ์struct์ด๊ธฐ ๋๋ฌธ์ ํด๋์ค๋ ํด๋์ค ๋ฐ์ด๋ ํ๋กํ ์ฝ์ ํด๋นํ์ง ์์deinit์ ์ฌ์ฉํ ์ ์๊ณ , ๋ฐ์ด๋๋ฅผ ๋ฒ์ด๋๋ฉด ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ ๋น๋ ๊ฒ์ด๋ผ ์๊ฐํ๋ค.
- ์ด๋ค ์ ์ด ๋ฌธ์ ์ธ๊ฐ?
- ์์ 2๊ฐ์ง ๊ด์ ์ผ๋ก ๋ค์ํ ์ ๋ณด๋ฅผ ์ฐพ๊ณ , ์๋๋ฅผ ํด๋ณธ ๋์ ์์ ์ผ๋ก ๋์์ ์ด๊ฒ ์ด๋ค ๋ฌธ์ ์ธ์ง ๋ค์๊ธ ์๊ฐ์ ํ๋ค.
Instruments์์๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ธ์ฆํ ํ ์ ์ง๋๋ ์์์ ๋ณด์ด๋ ๋ฐ๋ฉด,Xcode Memory Report์Memory Usage๋ ์ ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ ์ ๋ณด์๋ค.- ๋ง์ฝ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ์
WebKit์ ์ฌ์ฉํจ์ ์์ด ์ฆ๊ฐ ์ถ์ธ๋ฅผ ๋ณด์๊ฑฐ๋,Leak์ด ๋ฐ์ํ๊ฑฐ๋,Xcode Memory Report์์๋ ๊ธ์ฆํ๋ ํ์์ ๋ณด์๋ค๋ฉด ์ฌ์ฉ์์ ๋ฌธ์ ๋ก ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๊ฐ ๋ฐ๋์ ํ์ํ์ง๋ง, ์ ๋ฐ์ ์ผ๋ก ์ ์์ธ๋ฐ ๋ฐํดInstruments์์๋ง ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ฆ๊ฐํ๋ ์ ์์ ๋ค๋ฅธ ๋ฐฉํฅ์ผ๋ก ์ ๊ทผํด์ผ ํจ์ ์์๋ค.
VM Tracker
- ์
Instruments์์๋ง ์ผ์ด๋ ๊น? ์ผ๋จ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ดVM Allocation์ด๊ธฐ ๋๋ฌธ์VM Tracker๋ก ์ธก์ ์ ํ๋ค. VM Tracker๋ก ์ธก์ ํ์ ๋,Dirty Size/Swapped Size/Resident Size์ธ ํํธ๋ก ์ ๋ณด๋ฅผ ์ป์ ์ ์๋ค.Resident Size 409 MiB / Dirty Size 67 MiB / Swapped 59 MiB- ๊ทธ๋ฌ๋
Type์์ ์ ๋ฐ์ ์ฐจ์งํ๋__Text,*Dirty*๋ฅผ ๋ณด๋ฉด ๊ฐ๊ฐ์ 220/0/0, 83/67/43์ ๋ฉ๋ชจ๋ฆฌ ๋ถํฌ๋ฅผ ๋ณผ ์ ์๋ค.
- ๊ฒฐ๋ก
VM Tracker์์์๋ ์ฑ์์ ์ฌ์ฉํDirty Memory์ ํฌ๊ธฐ๊ฐ ์๊ณ ,__Text์Resident Size๋WebCore๋ง ์๋ ๊ฒ์ด ์๋๋ผSwiftUI,UIKitCore,JavaScriptCore๊ฐ ๊ฐ๊ฐ ์กฐ๊ธ์ฉ ๊ฐ๊ณ ์๋ ๊ฒ์ผ๋ก ๋ณด์WebView๋ง์ ๋ฌธ์ ๋ผ๊ณ ๋ณด๊ธฐ๊ฐ ์ด๋ ค์ ๋ค.Instruments์์ ์๋ฎฌ๋ ์ดํฐ ์น ํ๊ฒฝ์ ์ธก์ ํ๊ธฐ ์ํด ํ์ํ ์์์ด๊ฑฐ๋,__Textํ์ ์์ ๋ดค๋ฏ์ดUIKit์WebView๋ฅผSwiftUI์ View๋ก ์ ํํ๋ ๊ณผ์ ์์ ์์์ด ํ์ํ์ ์๋ ์๋ค๊ณ ๋ณด์๋ค.Instruments์์์ ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ๋ฅผ ์ค์ด๊ฑฐ๋ ์์ธ์ ์ฐพ์ง๋ ๋ชปํ์ง๋ง, ๊ทธ ๊ณผ์ ์์Xcode์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ธฐ๋ฒ์ ๋ค์ํ๊ฒ ์ฌ์ฉํด๋ดค๋ค๋ ์ ,WebView๋ฅผ ๋ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ๋ฐฉํฅ์ ๋ค๋ฐฉ๋ฉด์ผ๋ก ์๋ํด๋ณผ ์ ์์๊ธฐ์ ์ป์ด๊ฐ ๊ฒ์ด ๋ง๋ค๊ณ ์๊ฐํ๋ค.
![]() |
|---|
| Instruments |
![]() |
![]() |
|---|---|
| ํด๋ฐ ์์ ๋ ๋ฉ๋ชจ๋ฆฌ | ํด๋ฐ ์์ ๋ ๋ฉ๋ชจ๋ฆฌ |
++ 6) ์ถ๊ฐ ์๊ฒฌ
- Xcode์ Instruments๊ฐ ์ธก์ ํ ๋๋ง๋ค ์ฝ๊ฐ์ ๋ค๋ฅธ ์์์ ๋ณด์ด๋ ๋ฏํ๋ค.
24. 03. 15.๊ธฐ์ค WebKit์ ์ฌ์ฉํ ๋ ํ ๋น๋์๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ฌ์ฉ์ด ๋๋ ๋ค ํ ๋น์ด ํด์ ๋๋ ๋ชจ์ต์ ๋ณผ ์ ์๋ค.
![]() |
|---|
| WebKit ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ ๋น๋์๋ค๊ฐ ํด์ ๋๋ ๋ชจ์ต |
๊ณ ๋ฏผํ ์ :
- ๊ธฐ์กด์๋ App Theme์ ์ค์ ํ๊ธฐ ์ํด
Assets์Color Set์ ๋ง๋ค๊ณ ,ThemeColors์case๋ฅผ ์ถ๊ฐํด์คฌ๋ค. ๊ทธ๋ฌ๋ ์๋ก์ด ์์์ ์ถ๊ฐํ๋ ค๊ณ ๋ณด๋, ์ด๋ฏธ ๊ตฌํํ ์์์ด๋ ์๋ก ์ถ๊ฐํ๋ ค๋ ์์ ๋ชจ๋ Apple์์ ์ ๊ณตํ๋SystemColor์ด๊ธฐ์ ์ด๋ ๊ฒ ๋ณต์กํ๊ฒ ์งํํ ํ์๊ฐ ์์๊น ์ถ์ ์๊ฐ์ด ๋ค์๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
AccentColor๋ฅผ ์ ๊ฑฐํ๊ณColor๊ฐ์ ๊ฐ๊ธฐ๋ก ๊ฒฐ์ ์ ํ๊ณ ์์ ์ ์งํํ๋ค.- ๊ทธ๋ ๋ค๋ฉด ํ์ํ ์ ์ด ๋ฌด์์ผ๊น? ์ด๋ฏธ ๋ฑ๋กํ
AccentColor์ด๋ฆ์ ๊ฐ๊ณ ์๋Assets์Color Set์ ๋ค ์ง์ฐ๊ณ ,ThemeColors์์Color๊ฐ์ ๊ฐ์ง๋๋ก ์ฝ๋๋ฅผ ๊ตฌ์ฑํ ๊ฒฐ๊ณผ ๋ค์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. Color๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋SwiftUI์import๊ฐ ํ์ํ๊ณ ,SettingsView์์ForEach๋กButton์ ์์ฑํ๊ธฐ ์ํด์๋CaseIterable์ด,background()์ ์์๋ก ์ฌ์ฉํ๊ธฐ ์ํด์๋ShapeStyle์ด ํ์ํ๋ค.ThemeColorsํ์ผ์Foundation๋์SwiftUI๋ฅผimportํ๊ณ ,colorํ๋กํผํฐ๋ฅผ ์ถ๊ฐํ์ฌ ๊ฐcase์ ๋ง๋Color๋ฅผ ์ง์ ํด์ฃผ์๋ค. ๋ํCaseIterable์ ์ฑํํ์ฌForEach์์ ์ฌ์ฉํ ์ ์๋๋ก ํ๊ณ ,ShapeStyle์ ์ฑํํ์ฌbackground modifier์์ ์ฌ์ฉํ ์ ์๊ฒ ํ๋ค.themeColor.rawValue๋ก ์์์ ์ง์ ํ๋ ๋ถ๋ถ์themeColor.color๋ก ๋ฐ๊พธ๋ ์์ ๋ฑ ๋ง์ ์ฝ๋๋ฅผ ์์ ํด์ผ ํ์ง๋ง, ์ด๋ ๊ฒ ๋๊ท๋ชจ๋ก ์กฐ์ ํ ๊ฒฐ๊ณผOCP์์น์ ๋ ์ ์ค์ํ๊ฒ ๋์๋ค๊ณ ์๊ฐํ๋ค.- ๊ธฐ์กด์๋ ์๋ก์ด ์์์ ์ถ๊ฐํ๋ ค๋ฉด
Color Set์ ๋ง๋ค๊ณ ,ThemeColors์ ์๋ก์ดcase๋ฅผ ์ถ๊ฐํด์ค์ผ ํ๋ ๋ฐ๋ฉด ์ด์ ๋ ์๋ก์ด ์์์ ์ถ๊ฐํ๋ ๊ฒฝ์ฐThemeColors์์case์switch๋ง ๊ด๋ฆฌํด์ฃผ๋ฉด ๋๋ค.
import SwiftUI
enum ThemeColors: String, ShapeStyle, CaseIterable {
case pink = "Pink"
case blue = "Blue"
case purple = "Purple"
case indigo = "Indigo"
var color: Color {
switch self {
case .pink:
return .pink
case .blue:
return .blue
case .purple:
return .purple
case .indigo:
return .indigo
}
}
}๊ณ ๋ฏผํ ์ :
- ์ฑ ์ค์ ์ ํตํด ์์์ ๋ณ๊ฒฝํ์ง ์๋ ์ํ์์๋
NavigationBarTitleColor์ ์ง์ ์ดView+Extension์ ์ ์ํ ๋ฉ์๋๋ฅผ ํตํด ์ ์ด๋ค์ก๋ค. - ๊ทธ๋ฌ๋
SettingsView์์ThemeColor๋ฅผ ์กฐ์ ํ๊ณ ๋ณด๋NavigationBarTitleColor๋View๊ฐ ์์ ์๋ก ๊ทธ๋ ค์ง๊ธฐ ์ ๊น์ง ๋ค๋ฅธBinding๊ฐ์ ํตํด ๊ทธ๋ ค์ง๋ ์์ดํ ๊ณผ๋ ๋ฌ๋ฆฌ ์์ด ๋ณํ์ง ์์๋ค. ThemeColor์ ๋ฐ๋ผNavigationBarTitle๋ ์์ด ๋ณํ๊ธธ ์ํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ๊ธฐ์กด
NavigationBarTitle์ ์์์UIKit์apperanceํญ๋ชฉ์ ์ด์ฉํ๊ธฐ์ ์ข ๋ ์ ๋์ ์ผ๋ก,SwiftUI๋ต๊ฒ ์ฌ์ฉํ๊ณ ์ ์์ ๋ฐฉํฅ์ ๋ค๋ฅด๊ฒ ์งํํ๋ค. View์NavigationBarTitle๊ณผ ๋์ผํ ์ธ์์Text๋ฅผ ๋ณํ ๋ฐฐ์นํ์ฌ ์ด๋ฅผ ํด๊ฒฐํ๊ณ ,foregroundStyle()์ThemeColor๋ฅผ ์ ๋ฌํ์ฌ ์ฑ ์ค์ ์ ๋ง๊ฒ ์ ๋์ ์ผ๋ก ๋ณํ๊ฒ ๊ตฌํํ๋ค.ProgramView,SettingsView์์๋LargeTitle์ ๋ง๋ ๋์์ธ์View๋ฅผ ์ฌ์ฉ,SearchView์์๋.searchable modifier๊ฐ ์๊ธฐ ๋๋ฌธ์Toolbar๋ฅผ ํ์ฉํ์ฌtopBarLeading์title2 font๋ก ์ง์ ํText๋ฅผ ๋ฐฐ์นํ๋ค.
struct NavigationBarLargeTitleView: View {
// MARK: - Public Properties
var titleText: String
var themeColor: ThemeColors
// MARK: - Body
var body: some View {
Text(titleText)
.font(.largeTitle)
.bold()
.foregroundStyle(themeColor.color)
.padding(.leading)
}
}struct NavigationBarSmallTitleView: View {
// MARK: - Public Properties
var titleText: String
var themeColor: ThemeColors
// MARK: - Body
var body: some View {
Text(titleText)
.font(.title2)
.bold()
.foregroundStyle(themeColor.color)
}
}๊ณ ๋ฏผํ ์ :
SwiftData์Modelclass๋ฅผ ๊ตฌํ ํ ๋น๋๋ฅผ ์ํํ๋ ์์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches
SwiftData๋ ํธํ ๊ฐ๋ฅํ ์ ํ์ ์ฌ์ฉํ๋ ํ ํด๋์ค์ ๋ชจ๋ ๊ณ์ฐ ํ๋กํผํฐ๊ฐ ์๋ ํ๋กํผํฐ๋ฅผ ํฌํจํ๋ค.Bool,Int,String๊ณผ๊ฐ์ ๊ธฐ๋ณธ ์ ํ์ ๋ฌผ๋กstructure,enum๋ฐCodableํ๋กํ ์ฝ์ ์ค์ํ๋ ๊ธฐํ ๊ฐ ์ ํ๊ณผ ๊ฐ์ ๋ณต์กํ ๊ฐ ์ ํ๋ ์ง์ํ๋ค.- ์ฆ, ์ ์ค๋ฅ๊ฐ ๋๋ ๊ฒ์ ํด๋น ํ๋กํผํฐ๊ฐ
Codable์ ์ค์ํ์ง ์์ ๋ฐ์ํ๋ ๊ฒ์์ ์ ์ ์์๋ค. ProgramData๋ ์ด์ ๊น์ง ์ธ์ฝ๋ฉ์ ํ์ง ์์Decodable๋ง ์ค์ํ๊ธฐ ๋๋ฌธ์Codable๋ก ๋ณ๊ฒฝํด์ ํด๊ฒฐํ ์ ์์๋ค.
struct ProgramContentModel: Codable {
let category: String
let region: String
let title: String
let place: String
let organization: String
let target: String
let fees: String
let url: String
let imageURL: String
let startDate: String
let endDate: String
let longitude: String
let latitude: String
let isFree: String
}18. [SwiftData] error: Row (pk = ) for entity 'Model' is missing mandatory text data for property 'property name' ์ค๋ฅ
๊ณ ๋ฏผํ ์ :
setValue์ค๋ฅ๋ฅผ ํด๊ฒฐํ๊ณ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌSwiftData์์ CRUD ํ ์คํธ๋ฅผ ํ๋๋ฐ ์์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉฐ ๋ฐ์ดํฐ๊ฐ ์ ๋๋ก ์์ ์ด ๋์ง ์๋ ํ์์ด ๋ฐ์ํ๋ค.- ์ค์
Model์ ๋ชจ๋ ํญ๋ชฉ์ ํ์์ ์ผ๋ก ๋ฃ์ด์คฌ๊ณ , ์ค๋ฅ ์ํฉ์์๋ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๊ฐ์ ๋ ๋ชจ๋ ๊ฐ์ด ์์์ ํ์ธํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ์ฒ์์๋ ์ค๋ฅ์ property name์ด ๋ช
์๋์ด ์์ด ํด๋น ํ๋กํผํฐ์ ์ข
๋ฅ๋ฅผ ์ง์ ํ
enum์ ๋ฌธ์ ๋ผ๊ณ ํ๋จํ๋ค. - ๊ทธ๋์
enum์Codable๋ ์ ์ฉํด๋ณด๊ณ ๊ฒ์๋ ํด๋ณธ ๊ฒฐ๊ณผ ์๋์ ๊ฐ์ ์ ๋ณด๋ฅผ ์ป์ ์ ์์๋ค. - https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-17-release-notes
iOS & iPadOS 17 Release Notes์SwiftData Resolved Issues๋ฅผ ๋ณด๋ฉด ๋ค์์ ๋ด์ญ์ด ์๋ค.Fixed: Case value is not stored properly for a string rawvalue enumeration. (108634193)string์rawvalue๋ก ๊ฐ๋enum์์Case๊ฐ์ด ์ ์ ํ ์ ์ฅ๋์ง ์๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค๋๋ฐ ์ด๊ฒ ๋๋ฌธ์ ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ด๋ผ ์๊ฐํ๋ค.- ๊ทธ๋ฌ๋ iOS 17.0์์ ํด๊ฒฐ๋ ๋ฌธ์ ์ด๊ณ , ํ์ฌ ๋น๋ ์ํฉ์ Xcode 15.3, ์๋ฎฌ๋ ์ดํฐ iOS 17.4, ํ๋ก์ ํธ ์๊ตฌ์ฌํญ 17.0๋ก ๋ชจ๋ ํด๋น ์กฐ๊ฑด์ ๋ง์กฑํ๊ธฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ์ ๋ฌด๋ฆฌ๊ฐ ์์๋ค.
- ์ด์ธ์ ๋ค๋ฅธ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ง ๋ชปํด ์ง์ ์ด ์๋ ๋์ค, ํ๋ก์ ํธ์ ๋ฌธ์ ์ธ๊ฐ ์ถ์ด Xcode์์ ์๋ก์ด ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์๋ค.
- ํ๋ก์ ํธ ์ค์ ์์
SwiftData๋ฅผ ํฌํจํ ์ ์๋ ์ต์ ์ด ์์ด ์ฒดํฌํ์๊ณ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ๋ฉด๋ฐํ ์ดํด๋ณธ ๋ค, ๊ธฐ์กด ํ๋ก์ ํธ์ฒ๋ผ ๋ชจ๋ธ์ ๊ตฌ์ฑํด์ ํ ์คํธ ํด๋ดค๋ค. - ์๋ก์ด stub ํ๋ก์ ํธ์์๋ ์ ์์ ์ผ๋ก
SwiftData๊ฐ ์๋ํ๊ธฐ์ ์ด๋ค ์ฐจ์ด์ ์ด ์๋๊ฐ ๋ค์ ์ดํด๋ณด๋enum์ ๋ฌธ์ ๊ฐ ์๋์๋ค. ์ ์ด์SwiftData์ ๋ค์ด๊ฐ๋ ๋ฐ์ดํฐ๋ ํน์ enum์ด ์๋๋ผDTO๊ตฌ์กฐ์ฒด์๋๋ฐ ๋จ์ํ ๊ทธ ํ๋กํผํฐ ์ด๋ฆ์ ์ง์คํ๋ ๋ฐ๋์ ๊ทธ๊ฒ์ ์ ์ํenum์ด ๋ฌธ์ ์ผ ๊ฒ์ด๋ผ ์๊ฐํ ๊ฒ์ด๋ค. DTO๊ตฌ์กฐ์ฒด ๋ด๋ถ์JSON๋ฐ์ดํฐ๋ฅผ ๋์ฝ๋ฉํ ๋ ์ฐ๊ฒฐํด์ฃผ๊ธฐ ์ํดCodingKey๋ฅผ ์ฌ์ฉํ๋๋ฐ ์ด๊ฒ์ด ๋ฌธ์ ๊ฐ ๋ ๊ฒ์ด๋ค.- ๋ฐ๋ผ์
API๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋, ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ณ ๋์ ๋์ฝ๋ฉ๋DTO๋ชจ๋ธ์CodingKey๊ฐ ์๋ ๋ณ๋์ ๊ตฌ์กฐ์ฒด์ ๋งคํ์์ผ ์ ์ฅํจ์ผ๋ก์จ ํด๊ฒฐํ ์ ์์๋ค.
struct ProgramContent: Decodable {
let category: String
let region: String
let title: String
let place: String
let organization: String
let target: String
let fees: String
let url: String
let imageURL: String
let startDate: String
let endDate: String
let longitude: String
let latitude: String
let isFree: String
enum CodingKeys: String, CodingKey {
case category = "CODENAME"
case region = "GUNAME"
case title = "TITLE"
case place = "PLACE"
case organization = "ORG_NAME"
case target = "USE_TRGT"
case fees = "USE_FEE"
case url = "ORG_LINK"
case imageURL = "MAIN_IMG"
case startDate = "STRTDATE"
case endDate = "END_DATE"
case longitude = "LAT"
case latitude = "LOT"
case isFree = "IS_FREE"
}
}
/// SwiftData ๋ชจ๋ธ์์๋ CodingKey ์์๊ฐ ์ค๋ฅ๋ฅผ ์ ๋ฐํ์ฌ ๋ณ๋์ ๋งค์นญ ๊ตฌ์กฐ์ฒด ์ฌ์ฉ
struct ProgramContentModel: Codable {
let category: String
let region: String
let title: String
let place: String
let organization: String
let target: String
let fees: String
let url: String
let imageURL: String
let startDate: String
let endDate: String
let longitude: String
let latitude: String
let isFree: String
}programCancellable = URLSession.shared
.dataTaskPublisher(for: url)
.subscribe(on: DispatchQueue.global())
.map(\.data)
.decode(type: ProgramData.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink { completion in
print(completion)
} receiveValue: { [weak self] value in
self?.transformDTO(from: value.programInfo.programContents)
}private func transformDTO(from contents: [ProgramContent]) {
let today = Date().getStringOfTodayDate()
self.contents = contents
.filter { $0.endDate > today }
.sorted { $0.startDate < $1.startDate }
.map { content in
ProgramContentModel(
category: content.category,
region: content.region,
title: content.title,
place: content.place,
organization: content.organization,
target: content.target,
fees: content.fees,
url: content.url,
imageURL: content.imageURL,
startDate: content.startDate,
endDate: content.endDate,
longitude: content.longitude,
latitude: content.latitude,
isFree: content.isFree
)
}
}19. [WebKit] target is not running or doesn't have entitlement com.apple.runningboard.assertions.webkit / acquireSync Failed to acquire RBS assertion 'XPCConnectionTerminationWatchdog' for process with PID= ์ค๋ฅ
๊ณ ๋ฏผํ ์ :
- ํ๋ก์ ํธ๋ฅผ ์๋ฎฌ๋ ์ดํฐ์์ ํ ์คํธํ ๋ ๋ก๊ทธ์ฐฝ์ ์์ ๊ฐ์ ๋ฉ์์ง๊ฐ ์ง์์ ์ผ๋ก ์ถ๋ ฅ๋์๋ค. ์ด๋ค ๋ฌธ์ ์ธ์ง ์๊ณ ํด๊ฒฐํ๊ณ ์ ํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ๊ด๋ จํ ์ ๋ณด๊ฐ ๋ง์ด ์์ง ์์์ง๋ง Apple ํฌ๋ผ์์ ๋ต์ ์ป์ ์ ์์๋ค.
- https://developer.apple.com/forums/thread/742739
- 2์ฃผ ์ (
24. 03. 11. ๊ธฐ์ค)์ ์ฌ๋ผ์จ ํด๋น ๋ต๋ณ์ ๋ฐ๋ฅด๋ฉด Apple์ ํผ๋๋ฐฑ์ ์ฌ๋ฆฐ ๊ฒฐ๊ณผ ์๋ฌ๊ฐ ์๋ ๋จ์ ๋ก๊ทธ ๋ฉ์์ง์ด๊ณ , ์ดํ ์ ๋ฐ์ดํธ์์ ์ ๊ฑฐ๋๋ค๊ณ ์๋ต์ ๋ฐ์๋ค๊ณ ํ๋ค. - ๋น๋ ํ๊ฒฝ(Xcode 15.3) ๊ธฐ์ค ์์ง ํด๋น ๋ฉ์ธ์ง๋ ์ถ๋ ฅ๋๊ณ ์๋ค
๊ณ ๋ฏผํ ์ :
- ์๋ฆผ ํ ๊ธ์ ๊ฐ์ ์ฑ ๋ด์์ ๋ณ๊ฒฝํ๋ ๊ฒ์ด ์๋๋ผ ๊ธฐ์กด ์๋น์ค(์คํ๋ฒ ์ค, ์ด๋์ผ ๋ฑ)์ฒ๋ผ ๋๋ฐ์ด์ค ์๋ฆผ ์ค์ ์ ๋ฐ์ํ๊ณ ์ ํ๋ค.
- ๊ทธ๋ฌ๋ ์ค์ ์ฑ์ผ๋ก ๊ฐ๋ค๊ฐ ๋์์์ ๋ ์ํ๊ฐ ์ค์๊ฐ ๋ฐ์์ด ๋์ง ์์์ ํ์ธํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
UNUserNotificationCenter์์getNotificationSettings๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๊ฐ์ ๊ฐ์ ธ์ค๋ ๊ฒ์ ์ค์๊ฐ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์๋ค.- ๊ธฐ์กด์๋
NotificationManager์Publish๊ฐ์ ํ ๊ธ์Binding์ ํ์ผ๋ ํ ๊ธ๋กManager์ ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด ์๋๊ธฐ์Binding๋์ ๋ณ๋์Stateํ๋กํผํฐ๋ก ๋ง๋ค๊ณ ,Manager์ ๊ฐ์ ๋์ ํ๋ ์์ผ๋ก ์งํ์ ํ์๋ค. - ์ดํ ์์
์ ๋ฐ๋ผ
Manager์์ ํ์ฌ ๊ถํ ์ํ๋ฅผ ํ์ธํ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌManager์status๊ฐ์ ๋ฐ๊พธ๊ณ , ๊ทธ ๊ฐ์ ๋ค์ ํ ๊ธ ์ํ ๊ฐ์ผ๋ก ๋๊ฒจ์ฃผ๋ ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒ์ด๋ค. - ๊ทธ๋ฌ๋
onReceive์Combine์ ์ฌ์ฉํด์ ์ฑ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํฌ๊ทธ๋ผ์ด๋๋ก ๋์ด์ค๋ ๊ฒ์ ํ์ธํ๊ณ ๋ฉ์๋๋ฅผ ํธ์ถํ์์๋ ๋น์ฅ ๋ฐ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค(์ง์ฐ ๋ณ๊ฒฝ). - ๊ฐ์ ๋ฌธ์ ๋ฅผ ๊ณ์ ๋ณด๋ค๋ณด๋ ๋ฌธ์ ์ ์ ์ฝ๊ฒ ๋ฐ๊ฒฌํ ์ ์์๊ณ , ์ ์ ํด์์ ๊ฐ์ง๋ฉฐ ์๊ฐ์ ์ ๋ฆฌํ๊ณ ๋ค์ ๋ณด๋ ๋ก์ง์ ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ ์ ์์๋ค.
- ์ ์ด์ ๋ฐ์ธ๋ฉํ์ง ์์ ๊ฒ์ด๋ผ๋ฉด
Manager์์Publishํ๋ ์ด์ ๋ ์์ ๋ฟ๋๋ฌBindingํด์ ๊ฐ์ ๋ณ๊ฒฝ์ํค๋๋ผ๋ ๋๋ ค๋์ผ๋ฉด ๋๋ ๊ฒ์ด๋ค. - ์ดํ ํ ๊ธ ๊ฐ์
Publishํ๋กํผํฐ์ ๋ฐ์ธ๋ฉํ ํ,Alert์์ ๋ซ๊ธฐ๋ฅผ ๋๋ฅด๋ฉด ํ ๊ธ ๊ฐ์ ๋ค์ ์์์น๋ก ๋๋ ค๋๊ณ , ์ค์ ์ฐฝ์ผ๋ก ์ด๋ํ๋ค๋ฉด.onReceive๋ฅผ ํ์ฉํ์ฌ ๋ค์ ์ฑ์ดForeground๋ก ๋์์ฌ ๋notificationManager์ ์ํ ํ์ธ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ค. ๋ฉ์๋๊ฐ ์ํ๋ฅผ ํ์ธํ๊ณPublishํ๋กํผํฐ์ ๋ฐ์ํ๋ฉด, ๊ทธ๊ฒ์Bindingํ ํ ๊ธ์ ๊ฐ๋ ์๋์ผ๋ก ์ ๋ฐ์ดํธ๊ฐ ๋๋ ๊ฒ์ด๋ค. ์ด๋ ๊ฒ ๋ณ๊ฒฝํ ์ดํ ์ค์ ์ฑ์์ ๋ณ๊ฒฝํ ๊ฐ์ ๋ง๊ฒ ์ค์๊ฐ์ผ๋ก ๋ฐ์์ด ๋์๋ค.
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
DispatchQueue.main.async {
notificationManager.setNotificationStatus()
}
}
.onTapGesture {
isAlertPresented.toggle()
}
.alert(isPresented: $isAlertPresented) {
Alert(
title: Text("์๋ฆผ ์ค์ ๋ณ๊ฒฝ ์๋ด"),
message: Text("์๋ฆผ ์ค์ ๋ณ๊ฒฝ์ ์ค์ ์ฑ์์ ํ ์ ์์ด์."),
primaryButton: .default(Text("์ค์ "), action: {
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(settingsURL)
}),
secondaryButton: .cancel(Text("๋ซ๊ธฐ"), action: {
DispatchQueue.main.async {
isToggleOn.toggle()
}
}))
}๊ณ ๋ฏผํ ์ :
- ํ๋ก๊ทธ๋จ์ ์์์ผ๊ณผ ์ค๋ ๋ ์ง๋ฅผ ๋น๊ตํ๊ณ ์ ํ๋๋ฐ, ๊ธฐ์ค ์์ (UTC vs KST)์ด ๋ฌ๋ผ ๋ง์ถฐ์ฃผ๊ณ ์ ํ๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
DateFormatter์Locale,TimeZone์ ๋ค์ํ ๊ฐ์ผ๋ก ๋ณ๊ฒฝํด๋, String์ผ๋ก๋ ์ ์์ ์ผ๋ก ํ๊ตญ ์๊ฐ์ด ๋์ค๋ ๋ฐ๋ฉด Date ๊ฐ์ผ๋ก๋UTC๋ง ๋์ค๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.- ์ด๋ ํ ๊ฐ์ ๋ฐ์ํด๋ ์ํ๋ ๊ฐ์ด ๋์ค์ง ์์
addingTimeInterval์ ํตํด 9์๊ฐ์ ๋ํด ํด๊ฒฐํ๋ค.
extension Date {
func getKSTDate() -> Date {
return self.addingTimeInterval(60 * 60 * 9)
}
}๊ณ ๋ฏผํ ์ :
- ๊ธฐ์กด์๋ ์ต๋ ํธ์ถ ๊ฐ์์ธ 1000๊ฐ๋งํผ ๋ถ๋ฌ์์ ์ต๊ทผ ๋ฐ์ดํฐ ์์ผ๋ก ์ ๋ ฌํ์ฌ ์ฌ์ฉํ์๋ค.
- ๊ทธ๋ฌ๋ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ ๊ณณ์์ ๋ฐ์ดํฐ๊ฐ ์ ๋ฐ์ดํธ ๋ ๋๋ง๋ค ์ต๊ทผ ๋ฐ์ดํฐ๊ฐ ๋ค๋ก ๋์ด๊ฐ๋ ๋ฑ์ ์ด์๊ฐ ๋ฐ์ํ์ฌ ์ข ๋ฃ๊ฐ ๋์ง ์์์์๋ ์ฑ์ ํ์๋์ง ์๋ ๋ฌธ์ ๋ฅผ ํ์ธํ๋ค.
- ์ฌ์ฉ ์ง์์๋ต์๋ ์ ๋๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด์๋ ์ ์ฒด ๋ฐ์ดํฐ ๊ฐ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ ์ฒด ํธ์ถ ํ์๋ฅผ ๊ฒฐ์ ํด์ผ ํ๋ค๋ ๋ต๋ณ์ด ์์๋ค.
Combineํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ฌURLSession์dataTaskPublisher๋กAPI๋ฅผ ๋จ์ผ ํธ์ถํ์์ผ๋ ์ฌ๋ฌ ๋ฒ ํธ์ถํ๊ณ , ๊ทธ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ์์ ์ ํธ๋ค๋งํ๋ ๋ฐฉ๋ฒ์ ๋ชจ์ํ์๋ค.
๊ณผ์ ๋ฐ ํด๊ฒฐ :
dataTaskPublisher๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ๋ ๊ฒ์ ๋ถํ์ํ ์์ ์ด๋ผ๊ณ ์ฌ๊ฒจ์ ธ์Combine์์ ์ ๊ณตํ๋ ๋ฉ์๋๋ฅผ ์ฐพ์๋ณด๊ฒ ๋์๋ค.CombineLatest,Merge๊ฐ ์ด๋ฐ ์์ ์ ์ ํฉํ์ง๋ง, ๊ฐ๊ฐ ๊ฐ์๊ฐ ์ ํด์ ธ ์๋ค๋ฉด ์ฌ์ฉํ ์ ์๊ฒ ์ผ๋ ์ด ํ๋ก์ ํธ์์ API ํธ์ถ ํ์๋ ์ฒ์ ํ ์คํธ ์ธ๋ฑ์ค๋ฅผ ์ ์กํด ๋ฐ์ ์ ์ฒด ๋ฐ์ดํฐ ๊ฐ์์ ๋น๋กํ๊ธฐ ๋๋ฌธ์ ์ ์ฉํ๊ธฐ ์ด๋ ค์ ๋ค.- ๋ค์์ผ๋ก ์ฐพ์ ๋ฉ์๋๋
MergeMany์๋ค. ์ด๊ฒ์publisher์ ๋ฐฐ์ด์ ๋ฐ์ ๋ณํฉํด์ฃผ๋ ๊ฒ์ผ๋ก ์ ๋ ๋ฉ์๋์ ๋ค๋ฅด๊ฒ ๊ฐ์๋ฅผ ๋ชฐ๋ผ๋ ์ฌ์ฉํ ์ ์์๋ค. - Publishers.MergeMany
- ๊ทธ๋ฌ๋ ๊ณต์๋ฌธ์์ ์ ๋ณด๋ ํ๊ณ๊ฐ ์์๊ณ , ์ค์ฌ์ฉ ์์ ๋ฅผ ๋ด๋ ๋ค ์ฌ์ฉํ๋ ์ผ์ด์ค๊ฐ ๋ฌ๋ผ ๊ทธ๊ฒ๋ค์ ๊ดํตํ๋ ์ ๋ณด๋ฅผ ์ป๊ธฐ ์ด๋ ค์ ๋ค.
- ๊ทธ๋์ ์ฐ์ ํ์ํ ๊ฒ์ ํ๋ํ๋ ์ ๋ฆฌํ๋ค.
MergeMany์ ์ ๋ฌํpublisher๋ฐฐ์ด์ ๋ง๋ค์๊ณ , ๊ทธ๊ฒ์ ๋ค์ด๊ฐ ํ์ ์ ๊ฒฐ์ ํด์ผํ๋ค. - ์ด์ ์ ํธ์ถํ ๋๋
AnyCancellableํ์ ์ ๋ณ์์ ์ ์ฅํด ํ์์cancelํ๋ ์์ผ๋ก ํ์ง๋งAnyCancellable์ ์ด ์ํฉ์์ ๋ง์ง ์๋ ํ์ ์ด๋ผ๊ณ ์ฌ๊ฒผ๊ณ (MergeMany์์ ๋ฐ์ดํฐ ํธ๋ค๋ง์ด ๋ถ๊ฐ, ๊ณ ๋ฏผ ๋์URLSession.DataTaskPublisherํ์ ์ ๋ฃ๊ฒ ๋์๋ค. - ์ดํ ์์ฑํด์ผ ํ๋ ํ์๋ฅผ ๊ตฌํ ๋ค,
for๋ฌธ์ ์ฌ์ฉํ์ฌ ํธ์ถํ๋ ํ์์ ๋น๋กํ ์ธ๋ฑ์ค URL์ ์ง๋DataTaskPublisher๋ฅผ ์์ฑํ๊ณ ๋ฐฐ์ด์ ์ถ๊ฐํ๋ค. - ๊ทธ ๋ค,
Publishers.MergeMany์ ๋ฐฐ์ด์ ์ ๋ฌํ๊ณ , ๊ธฐ์กด์dataTaskPublisher์์ ํ๋ ๊ฒ์ฒ๋ผ ์ค๋ ๋ ์ค์ ๊ณผ ๋์ฝ๋ฉ ํ์ ์ค์ ,sink/receiveValue๊ด๋ จ ์ฝ๋๋ฅผ ์์ฑํ์๋ค. - ์ด ๊ณผ์ ์ผ๋ก ๋์ด์ค๊ธฐ๊น์ง ์ค๋ ๊ณ ๋ฏผ๊ณผ ๋ง์ ํ
์คํธ๋ฅผ ํ์๋๋ฐ, ๋ธ๋ ์ดํฌ ํฌ์ธํธ๋ฅผ ์ฐ์ด ํ๋ํ๋ ์ดํด๋ณธ ๊ฒฐ๊ณผ,
MergeMany์์ ํ๋จ์ ์์ ์ ๋ฐ๋ณตํ๋ ๊ฒ์ ์๊ฒ ๋์๊ณ ๊ทธ๋ ๋ค๋ฉด ๊ธฐ์กดdataTaskPublisher๋ฅผ ์ถ์ํ ํ๋ ๊ฒ๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ฉด ๋๊ฒ ๋ค๋ ํ๋จ์ ํ ์ ์์๋ค. URL์ ์ค์ ํ๋ ๋ถ๋ถ๊น์ง๋ฅผ ๋ถ๋ฆฌํ์ฌpublishers๋ฐฐ์ด์ ์ถ๊ฐํ๊ณ ,MergeMany์์์ ๊ณตํต ์์ ์ ๊ฐdataTaskPublisher์ ์ ์ฉํ ๊ฐ์ข ์์ ๋ค์ ๋ฃ๋ ๊ฒ์ด ๊ทธ๊ฒ์ด๋ค. - ๊ทธ๋ฆฌํ์ฌ ๊ฐ
publisher๋MergeMany์์ ์ ํด์ง ์ค๋ ๋์ ๋์ฝ๋ฉ, ๋ฐ์ดํฐ ํธ๋ค๋ง ๋ฐฉ์์ ๋ฐ๋ผ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๊ฒ ๋๋ค. ๋ชจ๋publisher์ ๋ํด์ ์ด ๊ณผ์ ์ด ๋๋๋ฉด, ๊ฐ๊ณต๋์ง ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํrawContentsํ๋กํผํฐ์์๋totalCount์ ๋น๊ตํ์ฌ ์๊ฐ ๋์ผํ ์ ๊ธฐ์กด์transformDTO์ ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ ๊ธฐ์กด์ฒ๋ผ ๋ฐ์ดํฐ๋ฅผ ํ๊ฐ๊ณตํ ์ ์๊ฒ ๋๋ค. - ๋คํ์ด์๋ ๊ฒ์ ๊ธฐ์กด ๋ฉ์๋๊ฐ ์ ๋ถ๋ฆฌ๊ฐ ๋์ด ์์ด์ 1000๊ฐ ๋จ์ผ ํธ์ถ์์ n๊ฐ n๋ฒ ํธ์ถ๋ก ๋ก์ง์ด ๋ณ๊ฒฝ๋์์์๋ ๋ถ๊ตฌํ๊ณ ํ๋์ ๋ฉ์๋๋ง ์ถ๊ฐํ ๋ฟ ๊ธฐ์กด ํ๋กํผํฐ๋ ๋ก์ง์ ๋ณ๊ฒฝํ ํ์๊ฐ ์์๋ค๋ ์ ์ด๋ค.
/// API์์ ๋ฐ์์์ผ ํ๋ ์ ์ฒด ๋ฐ์ดํฐ ์
private var totalCount = 0
/// API์์ ๋ฐ์์จ ์ ์ฒด ๋ฐ์ดํฐ. totalCount์ ์๊ฐ ๋ง์ ๋ transformDTO(from:)์ ๋ฐ์ดํฐ ์ ๋ฌ
private var rawContents = [ProgramContent]() {
didSet {
if rawContents.count == totalCount {
transformDTO(from: rawContents)
}
}
}
/// Publishers.MergeMany๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋์ ์ธ ํ์๋งํผ API๋ฅผ ํธ์ถํ๋ ๋ฉ์๋. ์ ๋ฌ๋ฐ์ ์์ ๋น๋กํ Publisher๋ฅผ ์์ฑํ๊ณ MergeMany๋ก ๊ฒฐํฉํ์ฌ ์ ์ฒด ๋ฐ์ดํฐ ํธ๋ค๋ง
private func getTotalContents(of amount: Int) {
let count = (amount + 999) / 1000
var publishers = [URLSession.DataTaskPublisher]()
/*
API ํธ์ถ ์ ์ธ๋ฑ์ค ๋ณํ ๋ก์ง
1ํ(0): 1...1000 = 0 * 1000 + 1...0 * 1000 + 1000
2ํ(1): 1001...2000 = 1 * 1000 + 1...1 * 1000 + 1000
3ํ(2): 2001...3000 = 2 * 1000 + 1...2 * 1000 + 1000
=> nํ(n): (n * 1000) + 1 ~ (n * 1000) + 1000
*/
for index in 0..<count {
guard let url = makeURL(startIndex: (index * 1000) + 1, endIndex: (index * 1000) + 1000) else { return }
let publisher = URLSession.shared
.dataTaskPublisher(for: url)
publishers.append(publisher)
}
programCancellable = Publishers.MergeMany(publishers)
.subscribe(on: DispatchQueue.global())
.map(\.data)
.decode(type: ProgramData.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink { completion in
print(completion)
} receiveValue: { value in
self.rawContents.append(contentsOf: value.programInfo.programContents)
}
}๊ณ ๋ฏผํ ์ :
- CalendarView์์ ๋ ์ง๋ณ ํ๋ก๊ทธ๋จ ๋ชฉ๋ก๊ณผ ํจ๊ป ์ฆ๊ฒจ์ฐพ๊ธฐ ์ฌ๋ถ๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด SwiftData์ ์ ์ฅํ ํญ๋ชฉ์ ์ฌ์ฉํ๋๋ฐ, ๊ทธ ํ๋ฉด์์ ProgramDetailView๋ก ์ง์
ํ๋ฉด CPU, ๋ฉ๋ชจ๋ฆฌ ๋ฆฌ์์ค๋ฅผ ๊ณผ๋ํ๊ฒ ์ฌ์ฉํ๋ ์ด์ ๋ฐ์

๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ์ฌ๋ฌ ๋ฒ ์๋ํ ๊ฒฐ๊ณผ, SwiftData๋ฅผ ์์ View์ธ CalendarView์์ ์ฌ์ฉํ๊ณ , ํ์ View์ธ ProgramOfTheDayView์์๋ ์ ๋ฌํ ์ฟผ๋ฆฌ ๊ฐ์ผ๋ก ์์ ์ ์ํํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์๋ค.
- ์ด์๋ฅผ ๋ณด์ฌ์ค ๊ตฌ๋ฌธ์ WebView์๊ณ , ์์ธ์ SwiftData ์ฝ๋์์ผ๋ ์ด๋ง์ ๋ ์๋ํ ๋๋ง๋ค ์ค๋ฅ ๋ฐ์ ์ฌ๋ถ๊ฐ ๊ณ ์ ์ ์ด์ง ์์๊ณ , Instruments, Xcode Debugger๋ ์ ๋ถ ํ๋ก์ธ์ค๊ฐ ๋ป์ด ์์ธ์ ์ ํํ ์ฐพ์ง ๋ชปํ ๊ฒ์ด ์์ฌ์ ๋ค.
- ๋ค๋ง SwiftData์ Query๋ฅผ ์กฐ๊ธ ๋ค๋ฅด๊ฒ ์ฌ์ฉํ๋๋ ํด๊ฒฐ๋ ๊ฒ์ผ๋ก ๋ณด์ subview์ ํจ๊ป ์ธ ๋ ์ด๋ค ์์ฉ์ ํ๋์ง ์ข ๋ ์์๋ณผ ํ์์ฑ์ ๋๊ผ๋ค.
๊ณ ๋ฏผํ ์ :
- ์ ํ ๊ธฐ๋ณธ ์ฑ์ฒ๋ผ ํญ๋ฐ๋ฅผ ํฐ์นํ์ฌ ์ด์ ํ๋ฉด์ผ๋ก ๋์๊ฐ๊ณ ์ถ์ผ๋ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด ์๊ณ , ๊ธฐ์กด์ ์ฌ์ฉ๋๋ ๋ฐฉ์๋ ๋ณต์กํ๊ฑฐ๋ ์ ์ฉ๋์ง ์์ ์ด์ฉ์ ๋ถํธํจ์ด ๋ฐ๋ฆ
- NavigationStack๊ณผ ํจ๊ป ๋์จ NavigationPath๋ฅผ ์ฌ์ฉํด๋ณด๊ณ ์ ํ์ผ๋ ์ค๋ณต ํ์ ์ค์ ์ ๋ฌธ์ ๋ฑ ์ ๋๋ก ์ฌ์ฉํ๊ธฐ๊ฐ ์ด๋ ค์ ์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ๋ฉ์๋๋ ๊ธฐ๊ตฌํ ์์๋ณด๋ค๋ ์๋ฆฌ๋ฅผ ๋ฐ๋ผ๊ฐ๊ณ ์ ํ๋ค. ๊ฒฐ๊ตญ ์คํ์ด ์์ด๋ ๋ฐฐ์ด์ด ํ์ํ๊ณ , ํญ๋ฐ๋ฅผ ๋๋ฅด๋ฉด ๊ทธ ๋ฐฐ์ด์ ์ด๊ธฐํ ํด์ผ ํ๋ค.
- enum์ผ๋ก ์ด๋ํ View์ ํ์ ์ caseํ ํ๊ณ , NavigationPath ๋์ ํด๋น enum์ ์์๋ก ๊ฐ๋ ๋ฐฐ์ด์ ํ์ฉํ๋ค.
- ๊ฐ NavigationStack์ ๊ฐ๋ 3๊ฐ์ ๋ฉ์ธ ํ๋ฉด์์ ๊ฐ path ๋ฐฐ์ด์ path๋ก ์ฌ์ฉํ๊ณ , ๊ธฐ์กด์ Destination์ ํฌํจํ NavigationLink ๋์ ์ด๋๋ก ๊ฐ ๊ฒ์ธ์ง DestinationPath value๋ง์ ๊ฐ์ง๋ NavigationLink๋ฅผ ์ฌ์ฉํ๋ค.
- ์ดํ ํด๋น Link๋ฅผ ์ ์ ํ ์ด์ฉํ๊ธฐ ์ํด navigationDestination์์ Path์ ๋ฐ๋ผ View๋ฅผ ์ฐ๊ฒฐ์์ผ์คฌ๋ค.
- ์ต์๋จ TabView์์๋ ํ์ฑํ ๋์ด ์๋ ํญ์์ ๋ค์ ํ ๋ฒ ํญ์ ํ๋ฉด path ๋ฐฐ์ด์ด ๋น์ด์๋์ง ํ์ธํ๊ณ , ๊ทธ๋ ์ง ์๋ค๋ฉด ๋น์ฐ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์์๋ค.
enum DestinationPath: Hashable {
case detail(ProgramContentModel), calendar, settings
}
// MARK: - @State Properties
@State private var selectedTab: Tab = .program
@State private var programPath: [DestinationPath] = []
@State private var favoritesPath: [DestinationPath] = []
@State private var searchPath: [DestinationPath] = []
// MARK: - Private Functions
private func tabSelection() -> Binding<Tab> {
Binding {
self.selectedTab
} set: { touchedTap in
if touchedTap == self.selectedTab {
switch touchedTap {
case .program:
if !programPath.isEmpty { programPath.removeAll() }
case .favorites:
if !favoritesPath.isEmpty { favoritesPath.removeAll() }
case .search:
if !searchPath.isEmpty { searchPath.removeAll() }
}
}
self.selectedTab = touchedTap
}
}
.navigationDestination(for: DestinationPath.self) { destination in
switch destination {
case .detail(let content): ProgramDetailView(themeColor: $themeColor, content: content)
case .calendar: CalendarView(contents: $networkManager.contents, themeColor: $themeColor)
case .settings: SettingsView(themeColor: $themeColor, selectedRegion: $selectedRegion)
}
}๊ณ ๋ฏผํ ์ :
- ์ค์ ํ๋ฉด์ ์ง์ ํ๊ฑฐ๋, ์ค์ ํ๋ฉด์์ ์ฑ ์์ ํน์ ์ง์ญ Picker๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด ํ๋ก๊ทธ๋จ ํ์ดํ, ์ค์ ํ์ดํ ์๋จ์ ์ฌ๋ฐฑ์ด ์๊ธฐ๋ ๋ฌธ์ ๊ฐ ๋ฐ์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- ๋๋ฒ๊น
ํ๋ฉด์ ํตํด ํ์ธํด๋ณด๋, ์๋จ์ ์๊ธฐ๋ ๊ณต๋ฐฑ์
NavigationBarLargeTitle์ ๋นUILabel๋๋ฌธ์ ์๊ธฐ๋ ๊ฒ์ด์๋ค. - ๋ณธ ์ฑ์์๋
NavigationBar Title๋์ ์ ์ฌํ๊ฒ ๊ตฌ์ฌํView๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๋ชจ์ข ์ ์ด์ ๋ก ํด๋นView์์ชฝ์Title์ด ์๊ธฐ๊ณ , ๊ทธ ๊ณต๊ฐ์ ์ฐจ์งํ๊ฒ ๋ ๊ฒ์ด๋ค. Title์ ๋น ๋ฌธ์์ด์ ๋ฃ์ด๋ดค์ง๋ง ํด๊ฒฐ๋์ง ์์๊ณ ,toolbar์Visibility๋ฅผ.hidden์ผ๋ก ์ค์ ํด ๋ดค์ผ๋ ํด๋ฐ ์ ์ฒด๊ฐ ์จ๊ฒจ์ ธ ์ฌ์ฉํ ์ ์์๋ค.- ๋์ ,
navigationBarTitleDisplayMode๋ฅผ.inline์ผ๋ก ์ค์ ํ์ฌ ์๋จ์ผ๋ก ์ฎ๊น์ผ๋ก์จ ํด๊ฒฐํ ์ ์์๋ค. - ๋๊ฐ์
NavigationStack์ ์ฌ์ฉํ๋ ์ฆ๊ฒจ์ฐพ๊ธฐ, ๊ฒ์ ํญ์์๋ ๋์ผํ ์ฆ์์ด ๋ฐ์ํ๋์ง ํ์ธํ์ผ๋, ๋ ํญ์์๋ ์์ ์ด ํด๋น ํญ์์๋ง ์ด๋ค์ง๊ณ , ์ด๋ํ๋๋ผ๋ ์ด๋ฏธinline์ผ๋ก ์ค์ ๋ProgramDetailView๋ก ๊ฐ๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ์์๋ค.
๊ณ ๋ฏผํ ์ :
- ๊ฒ์ ํ๋ฉด์์ ์นดํ ๊ณ ๋ฆฌ๋ ์ง์ญ์ ์ ํํ์ ๋ ํด๋น ์กฐ๊ฑด์ ๋ง๋ ํ๋ก๊ทธ๋จ์ด ์๋ค๋ฉด ์ฑ์ด ์ถฉ๋์ด ๋ ๊ฐ์ ์ข ๋ฃ ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- section์ด ์๋๋ฐ ์คํฌ๋กคํ๋ ค๋ ์๋(๊ธฐ์กด onChange ๋ด ๋ฉ์๋)๊ฐ ์์ด ๋ฐ์ํ๋ ๋ฌธ์ ๋ก ํ์ (Attempted to scroll the collection view to an out-of-bounds section (0) when there are only 0 sections)
- SearchView์์ body๋ฅผ ๊ตฌ์ฑํ ๋ filteredContents์ getFilteredContents ๋ฉ์๋์ ๋ฐํ๊ฐ์ ํ ๋นํ๊ณ , ์ด ๊ฐ์ ๋ฐ๋ผ View๋ฅผ ๊ตฌ์ฑํ๋๋ก ๋ณ๊ฒฝ
- filteredContents๊ฐ ๋น์ด์๋ค๋ฉด(ํํฐ ์กฐ๊ฑด์ ๋ง๋ ํ๋ก๊ทธ๋จ์ด ์๋ค๋ฉด) Text๋ก ํด๋น ๊ฒฐ๊ณผ๊ฐ ์์์ ์ฌ์ฉ์์๊ฒ ์๋ฆผ
- filteredContents๊ฐ ๋น์ด์์ง ์๋ค๋ฉด(ํํฐ ์กฐ๊ฑด์ ๋ง๋ ํ๋ก๊ทธ๋จ์ด ์๋ค๋ฉด) ๊ธฐ์กด๊ณผ ๊ฐ์ด List๋ก ํํ
๊ณ ๋ฏผํ ์ :
- ์ค์ ํ๋ฉด์ ์๋ฆผ ํ ๊ธ์ ์ฑ ์๋ฆผ ๊ถํ๊ณผ ๋๊ธฐํ๋์ด ์์
- ์ด์ ํ ๊ธ์ ํฐ์นํ๋ฉด ์์ดํฐ ๊ธฐ๋ณธ ์ค์ ์ฑ์ผ๋ก ๋์ด๊ฐ ์ ์๋ Alert ์ฐฝ์ด ํ์๋์ด์ผ ํจ
- ๊ทธ๋ฌ๋ ํ์ฌ ํ ๊ธ์ ํฐ์นํ๋ฉด ๋๊ธฐํ๋์ง ์๊ณ ์ฑ ๋ด์์ ์์ฒด์ ์ผ๋ก On/Off๋ฅผ ์กฐ์ ํ ์ ์์
๊ณผ์ ๋ฐ ํด๊ฒฐ :
- SettingsView์์ NotificationToggleView์ NotificationManager์ status๋ฅผ ๋ฐ์ธ๋ฉ ๊ฐ์ผ๋ก ๋๊ฒจ์คฌ์ผ๋ ๋ฐ์ดํฐ ๋๊ธฐํ๊ฐ ์ด๋ฃจ์ด์ง์ง ์๋ ๊ฒ์ผ๋ก ์ถ์ ๋์๋ค.
- ์ด์ SettingsView์์ ๋๊ฒจ์ฃผ๋ ๋์ NotificationToggleView์์ ์ง์ ๋ฐ์ธ๋ฉํ๊ธฐ๋ก ํ๋ค(SettingsView์์๋ ๋ณ๋์ NotificationManager๋ฅผ ์ฌ์ฉํ์ง ์์).
- ๊ธฐ์กด ์ฝ๋์์ isToggleOn์ ์ ๊ฑฐํ๊ณ , notificationManager์ status๋ฅผ ์ง์ ์ฌ์ฉํ๊ณ , bindingForToggle ํ๋กํผํฐ๋ฅผ ์์ฑํ์ฌ set์์ isAlertPresented๋ฅผ true๋ก ๋ณ๊ฒฝ์์ผ์ฃผ๋ ์์ ์ ์ถ๊ฐํ๋ค.
- ๋๋ถ์ด onAppear์ onReceive ํด๋ก์ ๋ ๋ฉ์ธ ์ค๋ ๋์์ ์คํ๋๊ธฐ ๋๋ฌธ์ GCD ์ฝ๋๋ฅผ ์ ๊ฑฐํ๋ค.
- SubView ๋ถ๋ฆฌ ๋ฑ ๋ชจ๋ํ์ ์ ๊ฒฝ์ ์ผ์ง๋ง ์์ง ์ ์ฒด์ ์ผ๋ก ๊ฐ์ ํ ์ ์ด ์์ด์ ์ฝ๋ ๊ฐ์ ์ ์ต์ฐ์ ๊ณผ์ ๋ก ์ผ๊ณ ์๋ค.



























