From 082c81c4930d701f56b7938d13952a100b02874d Mon Sep 17 00:00:00 2001 From: agilestarskim Date: Thu, 14 Sep 2023 18:14:27 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EB=82=A0=EC=94=A8=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 58 +++++++++++-- Diary/Application/SceneDelegate.swift | 1 + Diary/Controller/DiaryViewController.swift | 8 ++ .../DiaryContentCompositor.swift | 0 .../Interface/DiaryContentComposable.swift | 0 .../DiaryContentSegregator.swift | 0 .../Interface/DiaryContentSegregatable.swift | 0 .../Implementation/CurrentDateFormatter.swift | 0 .../Interface/DateFormattable.swift | 0 Diary/Model/JSONDecoder/WeatherDecoder.swift | 23 +++++ Diary/Model/Location/LocationManager.swift | 40 +++++++++ Diary/Model/Network/NetworkManager.swift | 55 ++++++++++++ Diary/Model/Persistance/DataManager.swift | 1 + Diary/Model/Weather/Weather.swift | 17 ++++ .../Diary.xcdatamodeld/.xccurrentversion | 2 +- .../Diary v2.xcdatamodel/contents | 9 ++ Diary/Resource/Info.plist | 2 + .../Model.xcmappingmodel/xcmapping.xml | 86 +++++++++++++++++++ Diary/View/DiaryCell.swift | 21 +++++ 19 files changed, 316 insertions(+), 7 deletions(-) rename Diary/{Model/Diary => Controller/Utils}/ContentHelper/Composite/Implementation/DiaryContentCompositor.swift (100%) rename Diary/{Model/Diary => Controller/Utils}/ContentHelper/Composite/Interface/DiaryContentComposable.swift (100%) rename Diary/{Model/Diary => Controller/Utils}/ContentHelper/Segregate/Implementation/DiaryContentSegregator.swift (100%) rename Diary/{Model/Diary => Controller/Utils}/ContentHelper/Segregate/Interface/DiaryContentSegregatable.swift (100%) rename Diary/{Model => Controller}/Utils/DateFormat/Implementation/CurrentDateFormatter.swift (100%) rename Diary/{Model => Controller}/Utils/DateFormat/Interface/DateFormattable.swift (100%) create mode 100644 Diary/Model/JSONDecoder/WeatherDecoder.swift create mode 100644 Diary/Model/Location/LocationManager.swift create mode 100644 Diary/Model/Network/NetworkManager.swift create mode 100644 Diary/Model/Weather/Weather.swift create mode 100644 Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents create mode 100644 Diary/Resource/Model.xcmappingmodel/xcmapping.xml diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 73329038a..af37d7297 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -14,6 +14,11 @@ 1F5F229A2AAA051200CB920D /* DiaryContentSegregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5F22992AAA051200CB920D /* DiaryContentSegregator.swift */; }; 1F5F229F2AAA07C400CB920D /* DateFormattable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5F229E2AAA07C400CB920D /* DateFormattable.swift */; }; 1F5F22A22AAA087A00CB920D /* CurrentDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5F22A12AAA087A00CB920D /* CurrentDateFormatter.swift */; }; + 1F99E86C2AB2E68400DA0038 /* Model.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 1F99E86B2AB2E68400DA0038 /* Model.xcmappingmodel */; }; + 1F99E86F2AB2E83A00DA0038 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F99E86E2AB2E83A00DA0038 /* LocationManager.swift */; }; + 1F99E8722AB2F62200DA0038 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F99E8712AB2F62200DA0038 /* NetworkManager.swift */; }; + 1F99E8752AB2F85300DA0038 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F99E8742AB2F85300DA0038 /* Weather.swift */; }; + 1F99E8782AB2F9CA00DA0038 /* WeatherDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F99E8772AB2F9CA00DA0038 /* WeatherDecoder.swift */; }; 1FDFB3B22AA607FE005128C3 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FDFB3B12AA607FE005128C3 /* DataManager.swift */; }; 5D2CF6FF2A9CBB4A009EECB3 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 5D2CF6FE2A9CBB4A009EECB3 /* .swiftlint.yml */; }; 5D2CF70D2A9F0520009EECB3 /* DiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D2CF70C2A9F0520009EECB3 /* DiaryViewController.swift */; }; @@ -33,6 +38,12 @@ 1F5F22992AAA051200CB920D /* DiaryContentSegregator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryContentSegregator.swift; sourceTree = ""; }; 1F5F229E2AAA07C400CB920D /* DateFormattable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormattable.swift; sourceTree = ""; }; 1F5F22A12AAA087A00CB920D /* CurrentDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentDateFormatter.swift; sourceTree = ""; }; + 1F99E86A2AB2DEE300DA0038 /* Diary v2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Diary v2.xcdatamodel"; sourceTree = ""; }; + 1F99E86B2AB2E68400DA0038 /* Model.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Model.xcmappingmodel; sourceTree = ""; }; + 1F99E86E2AB2E83A00DA0038 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; + 1F99E8712AB2F62200DA0038 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; + 1F99E8742AB2F85300DA0038 /* Weather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weather.swift; sourceTree = ""; }; + 1F99E8772AB2F9CA00DA0038 /* WeatherDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherDecoder.swift; sourceTree = ""; }; 1FDFB3B12AA607FE005128C3 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; 1FDFB3B42AA63E65005128C3 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/LaunchScreen.strings; sourceTree = ""; }; 5D2CF6FE2A9CBB4A009EECB3 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; @@ -78,6 +89,7 @@ 1F5CEE8A2A9CC45800521AB3 /* Controller */ = { isa = PBXGroup; children = ( + 1F5F229B2AAA076D00CB920D /* Utils */, C739AE28284DF28600741E8F /* MainViewController.swift */, 5D2CF70C2A9F0520009EECB3 /* DiaryViewController.swift */, ); @@ -91,6 +103,7 @@ C739AE30284DF28600741E8F /* Assets.xcassets */, C739AE35284DF28600741E8F /* Info.plist */, C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */, + 1F99E86B2AB2E68400DA0038 /* Model.xcmappingmodel */, ); path = Resource; sourceTree = ""; @@ -157,6 +170,7 @@ 1F5F229B2AAA076D00CB920D /* Utils */ = { isa = PBXGroup; children = ( + 1F5F228C2AAA03D000CB920D /* ContentHelper */, 1F5F229C2AAA078400CB920D /* DateFormat */, ); path = Utils; @@ -187,12 +201,36 @@ path = Implementation; sourceTree = ""; }; - 1FDFB3AF2AA60764005128C3 /* Diary */ = { + 1F99E86D2AB2E81800DA0038 /* Location */ = { isa = PBXGroup; children = ( - 1F5F228C2AAA03D000CB920D /* ContentHelper */, + 1F99E86E2AB2E83A00DA0038 /* LocationManager.swift */, ); - path = Diary; + path = Location; + sourceTree = ""; + }; + 1F99E8702AB2F61600DA0038 /* Network */ = { + isa = PBXGroup; + children = ( + 1F99E8712AB2F62200DA0038 /* NetworkManager.swift */, + ); + path = Network; + sourceTree = ""; + }; + 1F99E8732AB2F82C00DA0038 /* Weather */ = { + isa = PBXGroup; + children = ( + 1F99E8742AB2F85300DA0038 /* Weather.swift */, + ); + path = Weather; + sourceTree = ""; + }; + 1F99E8762AB2F9A900DA0038 /* JSONDecoder */ = { + isa = PBXGroup; + children = ( + 1F99E8772AB2F9CA00DA0038 /* WeatherDecoder.swift */, + ); + path = JSONDecoder; sourceTree = ""; }; 1FDFB3B02AA607EE005128C3 /* Persistance */ = { @@ -206,9 +244,11 @@ 5D2CF7062A9CC7C8009EECB3 /* Model */ = { isa = PBXGroup; children = ( - 1F5F229B2AAA076D00CB920D /* Utils */, + 1F99E8762AB2F9A900DA0038 /* JSONDecoder */, + 1F99E8702AB2F61600DA0038 /* Network */, + 1F99E86D2AB2E81800DA0038 /* Location */, 1FDFB3B02AA607EE005128C3 /* Persistance */, - 1FDFB3AF2AA60764005128C3 /* Diary */, + 1F99E8732AB2F82C00DA0038 /* Weather */, ); path = Model; sourceTree = ""; @@ -337,11 +377,15 @@ buildActionMask = 2147483647; files = ( 1F5F22942AAA049500CB920D /* DiaryContentCompositor.swift in Sources */, + 1F99E8752AB2F85300DA0038 /* Weather.swift in Sources */, 1F5F229F2AAA07C400CB920D /* DateFormattable.swift in Sources */, + 1F99E86F2AB2E83A00DA0038 /* LocationManager.swift in Sources */, 1F5F229A2AAA051200CB920D /* DiaryContentSegregator.swift in Sources */, + 1F99E8722AB2F62200DA0038 /* NetworkManager.swift in Sources */, C739AE29284DF28600741E8F /* MainViewController.swift in Sources */, 1F5F22972AAA04E500CB920D /* DiaryContentSegregatable.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, + 1F99E8782AB2F9CA00DA0038 /* WeatherDecoder.swift in Sources */, 5D2CF70D2A9F0520009EECB3 /* DiaryViewController.swift in Sources */, 1F5CEE8D2A9D97AF00521AB3 /* DiaryCell.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, @@ -349,6 +393,7 @@ 1F5F22A22AAA087A00CB920D /* CurrentDateFormatter.swift in Sources */, 1FDFB3B22AA607FE005128C3 /* DataManager.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, + 1F99E86C2AB2E68400DA0038 /* Model.xcmappingmodel in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -568,9 +613,10 @@ C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 1F99E86A2AB2DEE300DA0038 /* Diary v2.xcdatamodel */, C739AE2E284DF28600741E8F /* Diary.xcdatamodel */, ); - currentVersion = C739AE2E284DF28600741E8F /* Diary.xcdatamodel */; + currentVersion = 1F99E86A2AB2DEE300DA0038 /* Diary v2.xcdatamodel */; path = Diary.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Diary/Application/SceneDelegate.swift b/Diary/Application/SceneDelegate.swift index a04ff4317..9a2a18acf 100644 --- a/Diary/Application/SceneDelegate.swift +++ b/Diary/Application/SceneDelegate.swift @@ -11,6 +11,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? let dataManager: DataManager = DataManager() + func scene( _ scene: UIScene, willConnectTo session: UISceneSession, diff --git a/Diary/Controller/DiaryViewController.swift b/Diary/Controller/DiaryViewController.swift index 11a4d7989..122286096 100644 --- a/Diary/Controller/DiaryViewController.swift +++ b/Diary/Controller/DiaryViewController.swift @@ -17,6 +17,7 @@ final class DiaryViewController: UIViewController { private let compositor: DiaryContentComposable private let segregator: DiaryContentSegregatable private let currentFormatter: DateFormattable + private let locationManager: LocationManager // MARK: - Lifecycle @@ -35,6 +36,7 @@ final class DiaryViewController: UIViewController { self.compositor = DiaryContentCompositor() self.segregator = DiaryContentSegregator() + self.locationManager = LocationManager() super.init(nibName: nil, bundle: nil) } @@ -79,6 +81,12 @@ final class DiaryViewController: UIViewController { self.diary.content = text.content self.diary.createdDate = diary.createdDate ?? Date() + locationManager.fetchSingleLocation { location in + Task { + self.diary.weather = await NetworkManager.fetchCurrentWeather(coordinate: location.coordinate)?.icon + } + } + dataManager.saveContext() } diff --git a/Diary/Model/Diary/ContentHelper/Composite/Implementation/DiaryContentCompositor.swift b/Diary/Controller/Utils/ContentHelper/Composite/Implementation/DiaryContentCompositor.swift similarity index 100% rename from Diary/Model/Diary/ContentHelper/Composite/Implementation/DiaryContentCompositor.swift rename to Diary/Controller/Utils/ContentHelper/Composite/Implementation/DiaryContentCompositor.swift diff --git a/Diary/Model/Diary/ContentHelper/Composite/Interface/DiaryContentComposable.swift b/Diary/Controller/Utils/ContentHelper/Composite/Interface/DiaryContentComposable.swift similarity index 100% rename from Diary/Model/Diary/ContentHelper/Composite/Interface/DiaryContentComposable.swift rename to Diary/Controller/Utils/ContentHelper/Composite/Interface/DiaryContentComposable.swift diff --git a/Diary/Model/Diary/ContentHelper/Segregate/Implementation/DiaryContentSegregator.swift b/Diary/Controller/Utils/ContentHelper/Segregate/Implementation/DiaryContentSegregator.swift similarity index 100% rename from Diary/Model/Diary/ContentHelper/Segregate/Implementation/DiaryContentSegregator.swift rename to Diary/Controller/Utils/ContentHelper/Segregate/Implementation/DiaryContentSegregator.swift diff --git a/Diary/Model/Diary/ContentHelper/Segregate/Interface/DiaryContentSegregatable.swift b/Diary/Controller/Utils/ContentHelper/Segregate/Interface/DiaryContentSegregatable.swift similarity index 100% rename from Diary/Model/Diary/ContentHelper/Segregate/Interface/DiaryContentSegregatable.swift rename to Diary/Controller/Utils/ContentHelper/Segregate/Interface/DiaryContentSegregatable.swift diff --git a/Diary/Model/Utils/DateFormat/Implementation/CurrentDateFormatter.swift b/Diary/Controller/Utils/DateFormat/Implementation/CurrentDateFormatter.swift similarity index 100% rename from Diary/Model/Utils/DateFormat/Implementation/CurrentDateFormatter.swift rename to Diary/Controller/Utils/DateFormat/Implementation/CurrentDateFormatter.swift diff --git a/Diary/Model/Utils/DateFormat/Interface/DateFormattable.swift b/Diary/Controller/Utils/DateFormat/Interface/DateFormattable.swift similarity index 100% rename from Diary/Model/Utils/DateFormat/Interface/DateFormattable.swift rename to Diary/Controller/Utils/DateFormat/Interface/DateFormattable.swift diff --git a/Diary/Model/JSONDecoder/WeatherDecoder.swift b/Diary/Model/JSONDecoder/WeatherDecoder.swift new file mode 100644 index 000000000..390f6935f --- /dev/null +++ b/Diary/Model/JSONDecoder/WeatherDecoder.swift @@ -0,0 +1,23 @@ +// +// WeatherDecoder.swift +// Diary +// +// Created by 김민성 on 2023/09/14. +// + +import Foundation + +struct WeatherDecoder { + static func decode(jsonData: Data) -> Weather? { + do { + let decoder = JSONDecoder() + let weatherData = try decoder.decode(WeatherData.self, from: jsonData) + + return weatherData.weather.first + + } catch { + print("JSON 디코딩 에러: \(error)") + return nil + } + } +} diff --git a/Diary/Model/Location/LocationManager.swift b/Diary/Model/Location/LocationManager.swift new file mode 100644 index 000000000..f43b0ca24 --- /dev/null +++ b/Diary/Model/Location/LocationManager.swift @@ -0,0 +1,40 @@ +// +// LocationManager.swift +// Diary +// +// Created by 김민성 on 2023/09/14. +// + +import CoreLocation +import Foundation + +final class LocationManager: NSObject, CLLocationManagerDelegate { + private var locationManager = CLLocationManager() + private var locationCompletion: ((CLLocation) -> Void)? + + override init() { + super.init() + locationManager.delegate = self + locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters + } + + func fetchSingleLocation(completion: @escaping (CLLocation) -> Void) { + self.locationCompletion = completion + + locationManager.requestWhenInUseAuthorization() + locationManager.startUpdatingLocation() + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let location = locations.last else { + return + } + locationCompletion?(location) // 마지막으로 받은 위치 데이터 전달 + locationManager.stopUpdatingLocation() // 위치 업데이트 중지 + locationCompletion = nil // 완료 핸들러 제거 + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + + } +} diff --git a/Diary/Model/Network/NetworkManager.swift b/Diary/Model/Network/NetworkManager.swift new file mode 100644 index 000000000..bc6d2b8b0 --- /dev/null +++ b/Diary/Model/Network/NetworkManager.swift @@ -0,0 +1,55 @@ +// +// NetworkManager.swift +// Diary +// +// Created by 김민성 on 2023/09/14. +// + +import Foundation +import UIKit +import CoreLocation + +final class NetworkManager { + + static func fetchWeatherIcon(icon: String) async -> UIImage? { + let url = URL(string: "https://openweathermap.org/img/wn/\(icon)@2x.png")! + + do { + let (data, response) = try await URLSession.shared.data(from: url) + + guard let httpResponse = response as? HTTPURLResponse else { + return nil + } + + guard (200..<300) ~= httpResponse.statusCode else { + return nil + } + + return UIImage(data: data) + + } catch { + return nil + } + } + + static func fetchCurrentWeather(coordinate: CLLocationCoordinate2D) async -> Weather? { + let url = "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinate.latitude)&lon=\(coordinate.longitude)&appid=888d5726542be62a76b7fabed4352d4a" + do { + let (data, response) = try await URLSession.shared.data(from: URL(string: url)!) + + guard let httpResponse = response as? HTTPURLResponse else { + return nil + } + + guard (200..<300) ~= httpResponse.statusCode else { + return nil + } + + return WeatherDecoder.decode(jsonData: data) + + } catch { + return nil + } + + } +} diff --git a/Diary/Model/Persistance/DataManager.swift b/Diary/Model/Persistance/DataManager.swift index 9e282f0c4..da0da093f 100644 --- a/Diary/Model/Persistance/DataManager.swift +++ b/Diary/Model/Persistance/DataManager.swift @@ -10,6 +10,7 @@ import CoreData final class DataManager { lazy var container: NSPersistentContainer = { let container = NSPersistentContainer(name: "Diary") + container.loadPersistentStores(completionHandler: { ( _, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") diff --git a/Diary/Model/Weather/Weather.swift b/Diary/Model/Weather/Weather.swift new file mode 100644 index 000000000..f1b99b3ba --- /dev/null +++ b/Diary/Model/Weather/Weather.swift @@ -0,0 +1,17 @@ +// +// Weather.swift +// Diary +// +// Created by 김민성 on 2023/09/14. +// + +struct WeatherData: Decodable { + let weather: [Weather] +} + +struct Weather: Decodable { + let id: Int + let main: String + let description: String + let icon: String +} diff --git a/Diary/Resource/Diary.xcdatamodeld/.xccurrentversion b/Diary/Resource/Diary.xcdatamodeld/.xccurrentversion index d49fecccc..1d3080f41 100644 --- a/Diary/Resource/Diary.xcdatamodeld/.xccurrentversion +++ b/Diary/Resource/Diary.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Diary.xcdatamodel + Diary v2.xcdatamodel diff --git a/Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents b/Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents new file mode 100644 index 000000000..a7df82723 --- /dev/null +++ b/Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Diary/Resource/Info.plist b/Diary/Resource/Info.plist index 0eb786dc1..9c00a4c35 100644 --- a/Diary/Resource/Info.plist +++ b/Diary/Resource/Info.plist @@ -19,5 +19,7 @@ + NSLocationWhenInUseUsageDescription + "현재 위치 정보 제공을 허용하시겠습니까?" diff --git a/Diary/Resource/Model.xcmappingmodel/xcmapping.xml b/Diary/Resource/Model.xcmappingmodel/xcmapping.xml new file mode 100644 index 000000000..ac301b7fa --- /dev/null +++ b/Diary/Resource/Model.xcmappingmodel/xcmapping.xml @@ -0,0 +1,86 @@ + + + + + + 134481920 + AB99DB52-E6A6-44DE-98CC-2635D2922B55 + 107 + + + + NSPersistenceFrameworkVersion + 1152 + NSStoreModelVersionHashes + + XDDevAttributeMapping + + 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc= + + XDDevEntityMapping + + qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI= + + XDDevMappingModel + + EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ= + + XDDevPropertyMapping + + XN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA= + + XDDevRelationshipMapping + + akYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs= + + + NSStoreModelVersionHashesDigest + +Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A== + NSStoreModelVersionHashesVersion + 3 + NSStoreModelVersionIdentifiers + + + + + + + + + Diary/Resource/Diary.xcdatamodeld/Diary.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0 +cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCVAAsADAAbADcAOAA5AEEAQgBdAF4AXwBlAGYAcgCIAIkAigCLAIwAjQCOAI8AkACRAKoArQC0ALoAyQDYANsA6gD5APwAXAEMARsBHwEjATIBOAE5AUEBUAFZAWMBZAFlAWYBewF8AYQBhQGGAZIBpgGnAagBqQGqAasBrAGtAa4BvQHMAdsB3wHuAf0B/gINAhwCKwI3AkkCSgJLAkwCTQJOAk8CUAJfAm4CfQKMAo0CnAKrAroCwgLXAtgC4ALsAwADDwMeAy0DMQNAA08DXgNtA3wDiAOaA6kDuAPHA9YD1wPmA/UEBAQZBBoEIgQuBEIEUQRgBG8EcwSCBJEEoASvBL4EygTcBOsE+gUJBRgFJwU2BUUFRgVJBVIFVgVaBV4FZgVpBW0FblUkbnVsbNcADQAOAA8AEAARABIAEwAUABUAFgAXABgAFwAaXxAPX3hkX3Jvb3RQYWNrYWdlViRjbGFzc1xfeGRfY29tbWVudHNfEBBfeGRfbW9kZWxNYW5hZ2VyXxAVX2NvbmZpZ3VyYXRpb25zQnlOYW1lXV94ZF9tb2RlbE5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgJSAkYAAgJKAAICT3gAcAB0AHgAfACAAIQAiAA4AIwAkACUAJgAnACgAKQAqACsACQApABcALwAwADEAMgAzACkAKQAXXxAcWERCdWNrZXRGb3JDbGFzc2Vzd2FzRW5jb2RlZF8QGlhEQnVja2V0Rm9yUGFja2FnZXNzdG9yYWdlXxAcWERCdWNrZXRGb3JJbnRlcmZhY2Vzc3RvcmFnZV8QD194ZF9vd25pbmdNb2RlbF8QHVhEQnVja2V0Rm9yUGFja2FnZXN3YXNFbmNvZGVkVl9vd25lcl8QG1hEQnVja2V0Rm9yRGF0YVR5cGVzc3RvcmFnZVtfdmlzaWJpbGl0eV8QGVhEQnVja2V0Rm9yQ2xhc3Nlc3N0b3JhZ2VVX25hbWVfEB9YREJ1Y2tldEZvckludGVyZmFjZXN3YXNFbmNvZGVkXxAeWERCdWNrZXRGb3JEYXRhVHlwZXN3YXNFbmNvZGVkXxAQX3VuaXF1ZUVsZW1lbnRJRIAEgI+AjYABgASAAICOgJAQAIAFgAOABIAEgABQU1lFU9MAOgA7AA4APAA+AEBXTlMua2V5c1pOUy5vYmplY3RzoQA9gAahAD+AB4AlVURpYXJ53xAQAEMARABFAEYAIQBHAEgAIwBJAEoADgAlAEsATAAoAE0ATgBPACkAKQAUAFMAVAAxACkATgBXAD0ATgBaAFsAXF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgCyABIAEgAKACoCKgASACYCMgAaACYCLgAgIEv8HwrBXb3JkZXJlZNMAOgA7AA4AYABiAEChAGGAC6EAY4AMgCVeWERfUFN0ZXJlb3R5cGXZACEAJQBnAA4AKABoACMATQBpAD8AYQBOAG0AFwApADEAXABxXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgAeAC4AJgCuAAIAECIAN0wA6ADsADgBzAH0AQKkAdAB1AHYAdwB4AHkAegB7AHyADoAPgBCAEYASgBOAFIAVgBapAH4AfwCAAIEAggCDAIQAhQCGgBeAG4AcgB6AH4AhgCOAJoAqgCVfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGVhEUE1VbmlxdWVuZXNzQ29uc3RyYWludHN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VuhW7FbwVvRW/FcEVwhXDFcUVxhYFFgcWCRYLFg0WDhYPFhAWERYTFhUWFhYXFhkWGhZZFlsWXRZfFmEWYhZjFmQWZRZnFmkWahZrFm0WbhZxFrAWsha0FrYWuBa5FroWuxa8Fr4WwBbBFsIWxBbFFwQXBhcIFwoXDBcNFw4XDxcQFxIXFBcVFxYXGBcZF1gXWhdcF14XYBdhF2IXYxdkF2YXaBdpF2oXbBdtF3YXhBeRF58XrBe/F9YX6BgzGFYYdhiWGJgYmhicGJ4YoBihGKIYpBilGKcYqBiqGKwYrRiuGLAYsRi6GMcYzBjOGNAY1RjXGNkY2xkAGSQZSxlvGXEZcxl1GXcZeRl7GXwZfhmLGZwZnhmgGaIZpBmmGagZqhmsGb0ZvxnBGcMZxRnHGckZyxnNGc8aDhoQGhIaFBoWGhcaGBoZGhoaHBoeGh8aIBoiGiMaYhpkGmYaaBpqGmsabBptGm4acBpyGnMadBp2Gncathq4GroavBq+Gr8awBrBGsIaxBrGGscayBrKGssa2BrZGtoa3BsbGx0bHxshGyMbJBslGyYbJxspGysbLBstGy8bMBtvG3Ebcxt1G3cbeBt5G3obext9G38bgBuBG4MbhBvDG8UbxxvJG8sbzBvNG84bzxvRG9Mb1BvVG9cb2BwXHBkcGxwdHB8cIBwhHCIcIxwlHCccKBwpHCscLBxrHG0cbxxxHHMcdBx1HHYcdxx5HHscfBx9HH8cgBylHMkc8B0UHRYdGB0aHRwdHh0gHSEdIx0wHT8dQR1DHUUdRx1JHUsdTR1cHV4dYB1iHWQdZh1oHWodbB2rHa0drx2xHbMdtB21HbYdtx25HbsdvB29Hb8dwB3/HgEeAx4FHgceCB4JHgoeCx4NHg8eEB4RHhMeFB5THlUeVx5ZHlseXB5dHl4eXx5hHmMeZB5lHmceaB6nHqkeqx6tHq8esB6xHrIesx61HrceuB65HrsevB6/Hv4fAB8CHwQfBh8HHwgfCR8KHwwfDh8PHxAfEh8TH1IfVB9WH1gfWh9bH1wfXR9eH2AfYh9jH2QfZh9nH6YfqB+qH6wfrh+vH7AfsR+yH7Qfth+3H7gfuh+7IAYgKSBJIGkgayBtIG8gcSBzIHQgdSB3IHggeiB7IH0gfyCAIIEggyCEIIkgliCbIJ0gnyCkIKYgqCCqIM8g8yEaIT4hQCFCIUQhRiFIIUohSyFNIVohayFtIW8hcSFzIXUhdyF5IXshjCGOIZAhkiGUIZYhmCGaIZwhniHdId8h4SHjIeUh5iHnIegh6SHrIe0h7iHvIfEh8iIxIjMiNSI3IjkiOiI7IjwiPSI/IkEiQiJDIkUiRiKFIociiSKLIo0ijiKPIpAikSKTIpUiliKXIpkimiKnIqgiqSKrIuoi7CLuIvAi8iLzIvQi9SL2Ivgi+iL7Ivwi/iL/Iz4jQCNCI0QjRiNHI0gjSSNKI0wjTiNPI1AjUiNTI5IjlCOWI5gjmiObI5wjnSOeI6AjoiOjI6QjpiOnI+Yj6CPqI+wj7iPvI/Aj8SPyI/Qj9iP3I/gj+iP7JDokPCQ+JEAkQiRDJEQkRSRGJEgkSiRLJEwkTiRPJHQkmCS/JOMk5STnJOkk6yTtJO8k8CTyJP8lDiUQJRIlFCUWJRglGiUcJSslLSUvJTElMyU1JTclOSU7JXolfCV+JYAlgiWDJYQlhSWGJYgliiWLJYwljiWPJc4l0CXSJdQl1iXXJdgl2SXaJdwl3iXfJeAl4iXjJiImJCYmJigmKiYrJiwmLSYuJjAmMiYzJjQmNiY3JnYmeCZ6JnwmfiZ/JoAmgSaCJoQmhiaHJogmiiaLJsomzCbOJtAm0ibTJtQm1SbWJtgm2ibbJtwm3ibfJx4nICciJyQnJicnJygnKScqJywnLicvJzAnMiczJ3IndCd2J3gneid7J3wnfSd+J4AngieDJ4QnhieHJ5InmyecJ54npyeyJ8EnzCfaJ+8oAygaKCwoOSg6KDsoPShKKEsoTChOKFsoXChdKF8oaCh3KIQokyilKLko0CjiKOso7CjuKPso/Cj9KP8pACkJKRMpGgAAAAAAAAICAAAAAAAABXIAAAAAAAAAAAAAAAAAACki + + Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0 +cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCtAAsADAAbADcAOAA5AEEAQgBdAF4AXwBlAGYAcgCIAIkAigCLAIwAjQCOAI8AkACRAKoArQC0ALoAyQDYANsA6gD5APwAXAEMARsBHwEjATIBOAE5AUEBUAFZAWUBZgFnAWgBaQF+AX8BhwGIAYkBlQGpAaoBqwGsAa0BrgGvAbABsQHAAc8B3gHiAfECAAIBAhACHwIuAjoCTAJNAk4CTwJQAlECUgJTAmICcQKAAo8CkAKfAq4CvQLFAtoC2wLjAu8DAwMSAyEDMAM0A0MDUgNhA3ADfwOLA50DrAO7A8oD2QPaA+kD+AQHBBwEHQQlBDEERQRUBGMEcgR2BIUElASjBLIEwQTNBN8E7gT9BQwFGwUqBTkFSAVdBV4FZgVyBYYFlQWkBbMFtwXGBdUF5AXzBgIGDgYgBi8GPgZNBlwGawZ6BokGigaNBpYGmgaeBqIGqgatBrEGslUkbnVsbNcADQAOAA8AEAARABIAEwAUABUAFgAXABgAFwAaXxAPX3hkX3Jvb3RQYWNrYWdlViRjbGFzc1xfeGRfY29tbWVudHNfEBBfeGRfbW9kZWxNYW5hZ2VyXxAVX2NvbmZpZ3VyYXRpb25zQnlOYW1lXV94ZF9tb2RlbE5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgKyAqYAAgKqAAICr3gAcAB0AHgAfACAAIQAiAA4AIwAkACUAJgAnACgAKQAqACsACQApABcALwAwADEAMgAzACkAKQAXXxAcWERCdWNrZXRGb3JDbGFzc2Vzd2FzRW5jb2RlZF8QGlhEQnVja2V0Rm9yUGFja2FnZXNzdG9yYWdlXxAcWERCdWNrZXRGb3JJbnRlcmZhY2Vzc3RvcmFnZV8QD194ZF9vd25pbmdNb2RlbF8QHVhEQnVja2V0Rm9yUGFja2FnZXN3YXNFbmNvZGVkVl9vd25lcl8QG1hEQnVja2V0Rm9yRGF0YVR5cGVzc3RvcmFnZVtfdmlzaWJpbGl0eV8QGVhEQnVja2V0Rm9yQ2xhc3Nlc3N0b3JhZ2VVX25hbWVfEB9YREJ1Y2tldEZvckludGVyZmFjZXN3YXNFbmNvZGVkXxAeWERCdWNrZXRGb3JEYXRhVHlwZXN3YXNFbmNvZGVkXxAQX3VuaXF1ZUVsZW1lbnRJRIAEgKeApYABgASAAICmgKgQAIAFgAOABIAEgABQU1lFU9MAOgA7AA4APAA+AEBXTlMua2V5c1pOUy5vYmplY3RzoQA9gAahAD+AB4AlVURpYXJ53xAQAEMARABFAEYAIQBHAEgAIwBJAEoADgAlAEsATAAoAE0ATgBPACkAKQAUAFMAVAAxACkATgBXAD0ATgBaAFsAXF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgCyABIAEgAKACoCigASACYCkgAaACYCjgAgIEl6+fOpXb3JkZXJlZNMAOgA7AA4AYABiAEChAGGAC6EAY4AMgCVeWERfUFN0ZXJlb3R5cGXZACEAJQBnAA4AKABoACMATQBpAD8AYQBOAG0AFwApADEAXABxXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgAeAC4AJgCuAAIAECIAN0wA6ADsADgBzAH0AQKkAdAB1AHYAdwB4AHkAegB7AHyADoAPgBCAEYASgBOAFIAVgBapAH4AfwCAAIEAggCDAIQAhQCGgBeAG4AcgB6AH4AhgCOAJoAqgCVfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGVhEUE1VbmlxdWVuZXNzQ29uc3RyYWludHNfEBpYRF9QU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QGVhEX1BTS19mZXRjaFJlcXVlc3RzQXJyYXlfEBFYRF9QU0tfaXNBYnN0cmFjdF8QD1hEX1BTS191c2VySW5mb18QE1hEX1BTS19jbGFzc01hcHBpbmdfEBZYRF9QU0tfZW50aXR5Q2xhc3NOYW1l3xAPAJIAkwCUACEAlQCWAJcAIwCYAA4AJQCZAJoAKACbABcAnQAXAGMAXABcAFwAMQBcAKQAdABcAFwAFwBcVV90eXBlWF9kZWZhdWx0XF9hc3NvY2lhdGlvbltfaXNSZWFkT25seVlfaXNTdGF0aWNZX2lzVW5pcXVlWl9pc0Rlcml2ZWRaX2lzT3JkZXJlZFxfaXNDb21wb3NpdGVXX2lzTGVhZoAAgBiAAIAMCAgICIAagA4ICIAACNIAOwAOAKsArKCAGdIArgCvALAAsVokY2xhc3NuYW1lWCRjbGFzc2VzXk5TTXV0YWJsZUFycmF5owCwALIAs1dOU0FycmF5WE5TT2JqZWN00gCuAK8AtQC2XxAQWERVTUxQcm9wZXJ0eUltcKQAtwC4ALkAs18QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwBjAFwAXABcADEAXACkAHUAXABcABcAXIAAgACAAIAMCAgICIAagA8ICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAMsAFwBjAFwAXABcADEAXACkAHYAXABcABcAXIAAgB2AAIAMCAgICIAagBAICIAACNIAOwAOANkArKCAGd8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwBjAFwAXABcADEAXACkAHcAXABcABcAXIAAgACAAIAMCAgICIAagBEICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAOwAFwBjAFwAXABcADEAXACkAHgAXABcABcAXIAAgCCAAIAMCAgICIAagBIICIAACNIAOwAOAPoArKCAGd8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAP4AFwBjAFwAXABcADEAXACkAHkAXABcABcAXIAAgCKAAIAMCAgICIAagBMICIAACAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwEOABcAYwBcAFwAXAAxAFwApAB6AFwAXAAXAFyAAIAkgACADAgICAiAGoAUCAiAAAjTADoAOwAOARwBHQBAoKCAJdIArgCvASABIV8QE05TTXV0YWJsZURpY3Rpb25hcnmjASABIgCzXE5TRGljdGlvbmFyed8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXASUAFwBjAFwAXABcADEAXACkAHsAXABcABcAXIAAgCeAAIAMCAgICIAagBUICIAACNYAJQAOACgATQAhACMBMwE0ABcAXAAXADGAKIApgAAIgABfEBRYREdlbmVyaWNSZWNvcmRDbGFzc9IArgCvAToBO11YRFVNTENsYXNzSW1wpgE8AT0BPgE/AUAAs11YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAD0AFwBjAFwAXABcADEAXACkAHwAXABcABcAXIAAgAaAAIAMCAgICIAagBYICIAACNIArgCvAVEBUl8QElhEVU1MU3RlcmVvdHlwZUltcKcBUwFUAVUBVgFXAVgAs18QElhEVU1MU3RlcmVvdHlwZUltcF1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOgA7AA4BWgFfAECkAVsBXAFdAV6ALYAugC+AMKQBYAFhAWIBY4AxgFyAdICLgCVbY3JlYXRlZERhdGVXY29udGVudFV0aXRsZVd3ZWF0aGVy3xASAJIAkwCUAWoAIQCWAJcBawAjAJUBbACYAA4AJQCZAJoAKACbABcAFwAXACkAPwBcAFwBdAAxAFwATgBcAXgBWwBcAFwBfABcXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgDMIgAkIgFuALQgIgDIIEmCbK0rTADoAOwAOAYABgwBAogGBAYKANIA1ogGEAYWANoBKgCVfEBJYRF9QUHJvcFN0ZXJlb3R5cGVfEBJYRF9QQXR0X1N0ZXJlb3R5cGXZACEAJQGKAA4AKAGLACMATQGMAWABgQBOAG0AFwApADEAXAGUXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgDGANIAJgCuAAIAECIA30wA6ADsADgGWAZ8AQKgBlwGYAZkBmgGbAZwBnQGegDiAOYA6gDuAPIA9gD6AP6gBoAGhAaIBowGkAaUBpgGngECAQYBCgESARYBHgEiASYAlXxAbWERfUFBTS19pc1N0b3JlZEluVHJ1dGhGaWxlXxAbWERfUFBTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAQWERfUFBTS191c2VySW5mb18QEVhEX1BQU0tfaXNJbmRleGVkXxASWERfUFBTS19pc09wdGlvbmFsXxAaWERfUFBTS19pc1Nwb3RsaWdodEluZGV4ZWRfEBFYRF9QUFNLX2VsZW1lbnRJRF8QE1hEX1BQU0tfaXNUcmFuc2llbnTfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwD+ABcBhABcAFwAXAAxAFwApAGXAFwAXAAXAFyAAIAigACANggICAiAGoA4CAiAAAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwAXABcBhABcAFwAXAAxAFwApAGYAFwAXAAXAFyAAIAAgACANggICAiAGoA5CAiAAAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwHRABcBhABcAFwAXAAxAFwApAGZAFwAXAAXAFyAAIBDgACANggICAiAGoA6CAiAAAjTADoAOwAOAd8B4ABAoKCAJd8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAP4AFwGEAFwAXABcADEAXACkAZoAXABcABcAXIAAgCKAAIA2CAgICIAagDsICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAfMAFwGEAFwAXABcADEAXACkAZsAXABcABcAXIAAgEaAAIA2CAgICIAagDwICIAACAnfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwD+ABcBhABcAFwAXAAxAFwApAGcAFwAXAAXAFyAAIAigACANggICAiAGoA9CAiAAAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwAXABcBhABcAFwAXAAxAFwApAGdAFwAXAAXAFyAAIAAgACANggICAiAGoA+CAiAAAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwD+ABcBhABcAFwAXAAxAFwApAGeAFwAXAAXAFyAAIAigACANggICAiAGoA/CAiAAAjZACEAJQIvAA4AKAIwACMATQIxAWABggBOAG0AFwApADEAXAI5XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgDGANYAJgCuAAIAECIBL0wA6ADsADgI7AkMAQKcCPAI9Aj4CPwJAAkECQoBMgE2AToBPgFCAUYBSpwJEAkUCRgJHAkgCSQJKgFOAVIBVgFaAWIBZgFqAJV8QHVhEX1BBdHRLX2RlZmF1bHRWYWx1ZUFzU3RyaW5nXxAoWERfUEF0dEtfYWxsb3dzRXh0ZXJuYWxCaW5hcnlEYXRhU3RvcmFnZV8QF1hEX1BBdHRLX21pblZhbHVlU3RyaW5nXxAWWERfUEF0dEtfYXR0cmlidXRlVHlwZV8QF1hEX1BBdHRLX21heFZhbHVlU3RyaW5nXxAdWERfUEF0dEtfdmFsdWVUcmFuc2Zvcm1lck5hbWVfECBYRF9QQXR0S19yZWd1bGFyRXhwcmVzc2lvblN0cmluZ98QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwGFAFwAXABcADEAXACkAjwAXABcABcAXIAAgACAAIBKCAgICIAagEwICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAP4AFwGFAFwAXABcADEAXACkAj0AXABcABcAXIAAgCKAAIBKCAgICIAagE0ICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwGFAFwAXABcADEAXACkAj4AXABcABcAXIAAgACAAIBKCAgICIAagE4ICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAoIAFwGFAFwAXABcADEAXACkAj8AXABcABcAXIAAgFeAAIBKCAgICIAagE8ICIAACBEDhN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwGFAFwAXABcADEAXACkAkAAXABcABcAXIAAgACAAIBKCAgICIAagFAICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwGFAFwAXABcADEAXACkAkEAXABcABcAXIAAgACAAIBKCAgICIAagFEICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwGFAFwAXABcADEAXACkAkIAXABcABcAXIAAgACAAIBKCAgICIAagFIICIAACNIArgCvAr4Cv11YRFBNQXR0cmlidXRlpgLAAsECwgLDAsQAs11YRFBNQXR0cmlidXRlXFhEUE1Qcm9wZXJ0eV8QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QEgCSAJMAlALGACEAlgCXAscAIwCVAsgAmAAOACUAmQCaACgAmwAXABcAFwApAD8AXABcAtAAMQBcAE4AXAF4AVwAXABcAtgAXF8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJ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d9R33Hfgd+R37HfweOx49Hj8eQR5DHkQeRR5GHkceSR5LHkweTR5PHlAejx6RHpMelR6XHpgemR6aHpsenR6fHqAeoR6jHqQe4x7lHuce6R7rHuwe7R7uHu8e8R7zHvQe9R73Hvge+x86HzwfPh9AH0IfQx9EH0UfRh9IH0ofSx9MH04fTx+OH5Afkh+UH5Yflx+YH5kfmh+cH54fnx+gH6Ifox/iH+Qf5h/oH+of6x/sH+0f7h/wH/If8x/0H/Yf9yBCIGUghSClIKcgqSCrIK0gryCwILEgsyC0ILYgtyC5ILsgvCC9IL8gwCDFINIg1yDZINsg4CDiIOQg5iELIS8hViF6IXwhfiGAIYIhhCGGIYchiSGWIachqSGrIa0hryGxIbMhtSG3IcghyiHMIc4h0CHSIdQh1iHYIdoiGSIbIh0iHyIhIiIiIyIkIiUiJyIpIioiKyItIi4ibSJvInEicyJ1InYidyJ4InkieyJ9In4ifyKBIoIiwSLDIsUixyLJIsoiyyLMIs0izyLRItIi0yLVItYi4yLkIuUi5yMmIygjKiMsIy4jLyMwIzEjMiM0IzYjNyM4IzojOyN6I3wjfiOAI4IjgyOEI4UjhiOII4ojiyOMI44jjyPOI9Aj0iPUI9Yj1yPYI9kj2iPcI94j3yPgI+Ij4yQiJCQkJiQoJCokKyQsJC0kLiQwJDIkMyQ0JDYkNyR2JHgkeiR8JH4kfySAJIEkgiSEJIYkhySIJIokiySwJNQk+yUfJSElIyUlJSclKSUrJSwlLiU7JUolTCVOJVAlUiVUJVYlWCVnJWklayVtJW8lcSVzJXUldyW2JbgluiW8Jb4lvyXAJcElwiXEJcYlxyXIJcolyyYKJgwmDiYQJhImEyYUJhUmFiYYJhomGyYcJh4mHyZeJmAmYiZkJmYmZyZoJmkmaiZsJm4mbyZwJnImcyayJrQmtia4Jromuya8Jr0mvibAJsImwybEJsYmxycGJwgnCicMJw4nDycQJxEnEicUJxYnFycYJxonGydaJ1wnXidgJ2InYydkJ2UnZidoJ2onaydsJ24nbyeuJ7Ansie0J7Yntye4J7knuie8J74nvyfAJ8InwygOKDEoUShxKHModSh3KHkoeyh8KH0ofyiAKIIogyiFKIcoiCiJKIsojCiRKJ4ooyilKKcorCiuKLAosijXKPspIilGKUgpSilMKU4pUClSKVMpVSliKXMpdSl3KXkpeyl9KX8pgSmDKZQplimYKZopnCmeKaApoimkKaYp5SnnKekp6yntKe4p7ynwKfEp8yn1KfYp9yn5KfoqOSo7Kj0qPypBKkIqQypEKkUqRypJKkoqSypNKk4qjSqPKpEqkyqVKpYqlyqYKpkqmyqdKp4qnyqhKqIqryqwKrEqsyryKvQq9ir4Kvoq+yr8Kv0q/isAKwIrAysEKwYrBytGK0grSitMK04rTytQK1ErUitUK1YrVytYK1orWyuaK5wrniugK6IroyukK6UrpiuoK6orqyusK64rryvuK/Ar8iv0K/Yr9yv4K/kr+iv8K/4r/ywALAIsAyxCLEQsRixILEosSyxMLE0sTixQLFIsUyxULFYsVyx8LKAsxyzrLO0s7yzxLPMs9Sz3LPgs+i0HLRYtGC0aLRwtHi0gLSItJC0zLTUtNy05LTstPS0/LUEtQy2CLYQthi2ILYotiy2MLY0tji2QLZItky2ULZYtly3WLdgt2i3cLd4t3y3gLeEt4i3kLeYt5y3oLeot6y4qLiwuLi4wLjIuMy40LjUuNi44LjouOy48Lj4uPy5+LoAugi6ELoYuhy6ILokuii6MLo4ujy6QLpIuky7SLtQu1i7YLtou2y7cLt0u3i7gLuIu4y7kLuYu5y8mLygvKi8sLy4vLy8wLzEvMi80LzYvNy84LzovOy96L3wvfi+AL4Ivgy+EL4Uvhi+IL4oviy+ML44vjy+aL6MvpC+mL68vui/JL9Qv4i/3MAswIjA0MEEwQjBDMEUwUjBTMFQwVjBjMGQwZTBnMHAwfzCMMJswrTDBMNgw6jDzMPQw9jEDMQQxBTEHMQgxETEbMSIAAAAAAAACAgAAAAAAAAa2AAAAAAAAAAAAAAAAAAAxKg== + + + + + content + + + + Diary + Undefined + 1 + Diary + 1 + + + + + + createdDate + + + + title + + + + weather + + + \ No newline at end of file diff --git a/Diary/View/DiaryCell.swift b/Diary/View/DiaryCell.swift index fdfd442d6..b381b0154 100644 --- a/Diary/View/DiaryCell.swift +++ b/Diary/View/DiaryCell.swift @@ -31,6 +31,7 @@ final class DiaryCell: UICollectionViewListCell { private let horizontalStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal + stackView.distribution = .fill stackView.spacing = 24 return stackView }() @@ -64,6 +65,13 @@ final class DiaryCell: UICollectionViewListCell { return label }() + private lazy var weatherIcon: UIImageView = { + let image = UIImageView() + image.contentMode = .scaleAspectFit + return image + }() + + // MARK: - Internal Method func configureCell(diary: Diary, formatter: DateFormattable) { @@ -71,6 +79,7 @@ final class DiaryCell: UICollectionViewListCell { addSubviews() configureLabel(from: diary) constraintOuterStackView() + constraintWeatherIcon() } // MARK: - Private Method @@ -84,6 +93,7 @@ final class DiaryCell: UICollectionViewListCell { verticalStackView.addArrangedSubview(titleLabel) verticalStackView.addArrangedSubview(horizontalStackView) horizontalStackView.addArrangedSubview(createdDateLabel) + horizontalStackView.addArrangedSubview(weatherIcon) horizontalStackView.addArrangedSubview(contentLabel) accessories = [.disclosureIndicator()] @@ -93,6 +103,10 @@ final class DiaryCell: UICollectionViewListCell { titleLabel.text = diary.title contentLabel.text = diary.content createdDateLabel.text = currentFormatter?.format(date: diary.createdDate ?? Date(), style: .long) + Task { + weatherIcon.image = await NetworkManager.fetchWeatherIcon(icon: diary.weather ?? "10d") + } + } private func constraintOuterStackView() { @@ -103,4 +117,11 @@ final class DiaryCell: UICollectionViewListCell { verticalStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24) ]) } + + private func constraintWeatherIcon() { + NSLayoutConstraint.activate([ + weatherIcon.heightAnchor.constraint(equalToConstant: 24.0), + weatherIcon.widthAnchor.constraint(equalToConstant: 24.0) + ]) + } } From dd9f817649f28eb5fc1173e9af418c978081208f Mon Sep 17 00:00:00 2001 From: agilestarskim Date: Fri, 15 Sep 2023 16:08:59 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=B3=80=EA=B2=BD,=20=EA=B0=9D=EC=B2=B4=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20CoreData=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=9E=90=EC=B2=B4=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 52 ++++++++++----- Diary/Application/SceneDelegate.swift | 3 +- Diary/Controller/DiaryViewController.swift | 24 +++---- Diary/Controller/MainViewController.swift | 3 +- .../Utils/WeatherFetcher/WeatherFetcher.swift | 32 +++++++++ .../DataManager.swift | 10 ++- .../Diary.xcdatamodeld/.xccurrentversion | 0 .../Diary v2.xcdatamodel/contents | 2 +- .../Diary.xcdatamodel/contents | 0 .../Model.xcmappingmodel/xcmapping.xml | 0 .../Location/LocationManager.swift | 15 ++--- Diary/Model/Network/NetworkManager.swift | 55 ---------------- .../Utils}/WeatherDecoder.swift | 6 +- .../Weather/Utils/WeatherDecodingError.swift | 12 ++++ Diary/Model/Weather/Weather.swift | 3 - Diary/Network/NetworkError.swift | 15 +++++ Diary/Network/NetworkManager.swift | 65 +++++++++++++++++++ Diary/View/DiaryCell.swift | 29 +++++---- 18 files changed, 205 insertions(+), 121 deletions(-) create mode 100644 Diary/Controller/Utils/WeatherFetcher/WeatherFetcher.swift rename Diary/{Model/Persistance => CoreData}/DataManager.swift (75%) rename Diary/{Resource => CoreData}/Diary.xcdatamodeld/.xccurrentversion (100%) rename Diary/{Resource => CoreData}/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents (89%) rename Diary/{Resource => CoreData}/Diary.xcdatamodeld/Diary.xcdatamodel/contents (100%) rename Diary/{Resource => CoreData}/Model.xcmappingmodel/xcmapping.xml (100%) rename Diary/{Model => }/Location/LocationManager.swift (65%) delete mode 100644 Diary/Model/Network/NetworkManager.swift rename Diary/Model/{JSONDecoder => Weather/Utils}/WeatherDecoder.swift (71%) create mode 100644 Diary/Model/Weather/Utils/WeatherDecodingError.swift create mode 100644 Diary/Network/NetworkError.swift create mode 100644 Diary/Network/NetworkManager.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index af37d7297..a16409257 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 1F38B51E2AB4223D0010EB00 /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F38B51D2AB4223D0010EB00 /* NetworkError.swift */; }; + 1F38B5202AB424D00010EB00 /* WeatherDecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F38B51F2AB424D00010EB00 /* WeatherDecodingError.swift */; }; + 1F38B5232AB427090010EB00 /* WeatherFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F38B5222AB427090010EB00 /* WeatherFetcher.swift */; }; 1F5CEE8D2A9D97AF00521AB3 /* DiaryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5CEE8C2A9D97AF00521AB3 /* DiaryCell.swift */; }; 1F5F22922AAA043400CB920D /* DiaryContentComposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5F22912AAA043400CB920D /* DiaryContentComposable.swift */; }; 1F5F22942AAA049500CB920D /* DiaryContentCompositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5F22932AAA049500CB920D /* DiaryContentCompositor.swift */; }; @@ -31,6 +34,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 1F38B51D2AB4223D0010EB00 /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; + 1F38B51F2AB424D00010EB00 /* WeatherDecodingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherDecodingError.swift; sourceTree = ""; }; + 1F38B5222AB427090010EB00 /* WeatherFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherFetcher.swift; sourceTree = ""; }; 1F5CEE8C2A9D97AF00521AB3 /* DiaryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCell.swift; sourceTree = ""; }; 1F5F22912AAA043400CB920D /* DiaryContentComposable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryContentComposable.swift; sourceTree = ""; }; 1F5F22932AAA049500CB920D /* DiaryContentCompositor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryContentCompositor.swift; sourceTree = ""; }; @@ -69,6 +75,23 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1F38B51C2AB41EDB0010EB00 /* Utils */ = { + isa = PBXGroup; + children = ( + 1F99E8772AB2F9CA00DA0038 /* WeatherDecoder.swift */, + 1F38B51F2AB424D00010EB00 /* WeatherDecodingError.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 1F38B5212AB426B80010EB00 /* WeatherFetcher */ = { + isa = PBXGroup; + children = ( + 1F38B5222AB427090010EB00 /* WeatherFetcher.swift */, + ); + path = WeatherFetcher; + sourceTree = ""; + }; 1F5CEE882A9CC44A00521AB3 /* Application */ = { isa = PBXGroup; children = ( @@ -102,8 +125,6 @@ C739AE32284DF28600741E8F /* LaunchScreen.storyboard */, C739AE30284DF28600741E8F /* Assets.xcassets */, C739AE35284DF28600741E8F /* Info.plist */, - C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */, - 1F99E86B2AB2E68400DA0038 /* Model.xcmappingmodel */, ); path = Resource; sourceTree = ""; @@ -170,6 +191,7 @@ 1F5F229B2AAA076D00CB920D /* Utils */ = { isa = PBXGroup; children = ( + 1F38B5212AB426B80010EB00 /* WeatherFetcher */, 1F5F228C2AAA03D000CB920D /* ContentHelper */, 1F5F229C2AAA078400CB920D /* DateFormat */, ); @@ -213,6 +235,7 @@ isa = PBXGroup; children = ( 1F99E8712AB2F62200DA0038 /* NetworkManager.swift */, + 1F38B51D2AB4223D0010EB00 /* NetworkError.swift */, ); path = Network; sourceTree = ""; @@ -220,34 +243,25 @@ 1F99E8732AB2F82C00DA0038 /* Weather */ = { isa = PBXGroup; children = ( + 1F38B51C2AB41EDB0010EB00 /* Utils */, 1F99E8742AB2F85300DA0038 /* Weather.swift */, ); path = Weather; sourceTree = ""; }; - 1F99E8762AB2F9A900DA0038 /* JSONDecoder */ = { - isa = PBXGroup; - children = ( - 1F99E8772AB2F9CA00DA0038 /* WeatherDecoder.swift */, - ); - path = JSONDecoder; - sourceTree = ""; - }; - 1FDFB3B02AA607EE005128C3 /* Persistance */ = { + 1FDFB3B02AA607EE005128C3 /* CoreData */ = { isa = PBXGroup; children = ( + C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */, + 1F99E86B2AB2E68400DA0038 /* Model.xcmappingmodel */, 1FDFB3B12AA607FE005128C3 /* DataManager.swift */, ); - path = Persistance; + path = CoreData; sourceTree = ""; }; 5D2CF7062A9CC7C8009EECB3 /* Model */ = { isa = PBXGroup; children = ( - 1F99E8762AB2F9A900DA0038 /* JSONDecoder */, - 1F99E8702AB2F61600DA0038 /* Network */, - 1F99E86D2AB2E81800DA0038 /* Location */, - 1FDFB3B02AA607EE005128C3 /* Persistance */, 1F99E8732AB2F82C00DA0038 /* Weather */, ); path = Model; @@ -273,6 +287,9 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + 1FDFB3B02AA607EE005128C3 /* CoreData */, + 1F99E86D2AB2E81800DA0038 /* Location */, + 1F99E8702AB2F61600DA0038 /* Network */, 5D2CF7062A9CC7C8009EECB3 /* Model */, 1F5CEE8B2A9CC45D00521AB3 /* Resource */, 1F5CEE8A2A9CC45800521AB3 /* Controller */, @@ -376,6 +393,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1F38B5232AB427090010EB00 /* WeatherFetcher.swift in Sources */, 1F5F22942AAA049500CB920D /* DiaryContentCompositor.swift in Sources */, 1F99E8752AB2F85300DA0038 /* Weather.swift in Sources */, 1F5F229F2AAA07C400CB920D /* DateFormattable.swift in Sources */, @@ -384,6 +402,7 @@ 1F99E8722AB2F62200DA0038 /* NetworkManager.swift in Sources */, C739AE29284DF28600741E8F /* MainViewController.swift in Sources */, 1F5F22972AAA04E500CB920D /* DiaryContentSegregatable.swift in Sources */, + 1F38B5202AB424D00010EB00 /* WeatherDecodingError.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, 1F99E8782AB2F9CA00DA0038 /* WeatherDecoder.swift in Sources */, 5D2CF70D2A9F0520009EECB3 /* DiaryViewController.swift in Sources */, @@ -394,6 +413,7 @@ 1FDFB3B22AA607FE005128C3 /* DataManager.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, 1F99E86C2AB2E68400DA0038 /* Model.xcmappingmodel in Sources */, + 1F38B51E2AB4223D0010EB00 /* NetworkError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Diary/Application/SceneDelegate.swift b/Diary/Application/SceneDelegate.swift index 9a2a18acf..a90027e6b 100644 --- a/Diary/Application/SceneDelegate.swift +++ b/Diary/Application/SceneDelegate.swift @@ -10,8 +10,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? let dataManager: DataManager = DataManager() - - + func scene( _ scene: UIScene, willConnectTo session: UISceneSession, diff --git a/Diary/Controller/DiaryViewController.swift b/Diary/Controller/DiaryViewController.swift index 122286096..d53a41cf3 100644 --- a/Diary/Controller/DiaryViewController.swift +++ b/Diary/Controller/DiaryViewController.swift @@ -17,7 +17,7 @@ final class DiaryViewController: UIViewController { private let compositor: DiaryContentComposable private let segregator: DiaryContentSegregatable private let currentFormatter: DateFormattable - private let locationManager: LocationManager + private let weatherFetcher: WeatherFetcher // MARK: - Lifecycle @@ -27,17 +27,15 @@ final class DiaryViewController: UIViewController { ) { self.dataManager = dataManager self.currentFormatter = formatter + self.compositor = DiaryContentCompositor() + self.segregator = DiaryContentSegregator() + self.weatherFetcher = WeatherFetcher() if let diary = diary { self.diary = diary } else { self.diary = Diary(context: dataManager.container.viewContext) } - - self.compositor = DiaryContentCompositor() - self.segregator = DiaryContentSegregator() - self.locationManager = LocationManager() - super.init(nibName: nil, bundle: nil) } @@ -62,6 +60,9 @@ final class DiaryViewController: UIViewController { super.viewDidAppear(animated) if self.textView.text.isEmpty { textView.becomeFirstResponder() + self.weatherFetcher.fetch { pngData in + self.diary.weatherImage = pngData + } } } @@ -71,7 +72,7 @@ final class DiaryViewController: UIViewController { let trimmedText = self.textView.text.trimmingCharacters(in: .whitespacesAndNewlines) if trimmedText.isEmpty { - self.dataManager.container.viewContext.delete(self.diary) + self.dataManager.delete(self.diary) return } @@ -81,12 +82,6 @@ final class DiaryViewController: UIViewController { self.diary.content = text.content self.diary.createdDate = diary.createdDate ?? Date() - locationManager.fetchSingleLocation { location in - Task { - self.diary.weather = await NetworkManager.fetchCurrentWeather(coordinate: location.coordinate)?.icon - } - } - dataManager.saveContext() } @@ -177,8 +172,7 @@ final class DiaryViewController: UIViewController { ) let delete = UIAlertAction(title: "삭제", style: .destructive) { [weak self] _ in - self?.dataManager.container.viewContext.delete(diary) - self?.dataManager.saveContext() + self?.dataManager.delete(diary) self?.navigationController?.popViewController(animated: true) } diff --git a/Diary/Controller/MainViewController.swift b/Diary/Controller/MainViewController.swift index 85538881e..9c7267d1a 100644 --- a/Diary/Controller/MainViewController.swift +++ b/Diary/Controller/MainViewController.swift @@ -160,8 +160,7 @@ final class MainViewController: UIViewController { let deleteAlert = UIAlertController(title: "진짜요?", message: "정말로 삭제하시겠어요?", preferredStyle: .alert) let delete = UIAlertAction(title: "삭제", style: .destructive) { [weak self] _ in - self?.dataManager.container.viewContext.delete(diary) - self?.dataManager.saveContext() + self?.dataManager.delete(diary) self?.readDiaries() } diff --git a/Diary/Controller/Utils/WeatherFetcher/WeatherFetcher.swift b/Diary/Controller/Utils/WeatherFetcher/WeatherFetcher.swift new file mode 100644 index 000000000..7898f8562 --- /dev/null +++ b/Diary/Controller/Utils/WeatherFetcher/WeatherFetcher.swift @@ -0,0 +1,32 @@ +// +// WeatherFetcher.swift +// Diary +// +// Created by 김민성 on 2023/09/15. +// + +import Foundation +import UIKit + +struct WeatherFetcher { + let locationManager: LocationManager + + init(locationManager: LocationManager = LocationManager()) { + self.locationManager = locationManager + } + + func fetch(_ completion: @escaping (Data?) -> Void) { + locationManager.fetchSingleLocation { location in + Task { + do { + let weather = try await NetworkManager.fetchCurrentWeather(coordinate: location.coordinate) + + let icon = try await NetworkManager.fetchWeatherIcon(icon: weather?.icon) + completion(icon) + } catch { + completion(nil) + } + } + } + } +} diff --git a/Diary/Model/Persistance/DataManager.swift b/Diary/CoreData/DataManager.swift similarity index 75% rename from Diary/Model/Persistance/DataManager.swift rename to Diary/CoreData/DataManager.swift index da0da093f..6ac86c552 100644 --- a/Diary/Model/Persistance/DataManager.swift +++ b/Diary/CoreData/DataManager.swift @@ -19,6 +19,11 @@ final class DataManager { return container }() + func delete(_ diary: Diary) { + container.viewContext.delete(diary) + saveContext() + } + func saveContext () { let context = container.viewContext if context.hasChanges { @@ -33,8 +38,11 @@ final class DataManager { func fetch() -> [Diary] { let context = container.viewContext + let request = Diary.fetchRequest() + let createdDateSort = NSSortDescriptor(keyPath: \Diary.createdDate, ascending: false) + request.sortDescriptors = [createdDateSort] do { - return try context.fetch(Diary.fetchRequest()) + return try context.fetch(request) } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") diff --git a/Diary/Resource/Diary.xcdatamodeld/.xccurrentversion b/Diary/CoreData/Diary.xcdatamodeld/.xccurrentversion similarity index 100% rename from Diary/Resource/Diary.xcdatamodeld/.xccurrentversion rename to Diary/CoreData/Diary.xcdatamodeld/.xccurrentversion diff --git a/Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents b/Diary/CoreData/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents similarity index 89% rename from Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents rename to Diary/CoreData/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents index a7df82723..6b373057f 100644 --- a/Diary/Resource/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents +++ b/Diary/CoreData/Diary.xcdatamodeld/Diary v2.xcdatamodel/contents @@ -4,6 +4,6 @@ - + \ No newline at end of file diff --git a/Diary/Resource/Diary.xcdatamodeld/Diary.xcdatamodel/contents b/Diary/CoreData/Diary.xcdatamodeld/Diary.xcdatamodel/contents similarity index 100% rename from Diary/Resource/Diary.xcdatamodeld/Diary.xcdatamodel/contents rename to Diary/CoreData/Diary.xcdatamodeld/Diary.xcdatamodel/contents diff --git a/Diary/Resource/Model.xcmappingmodel/xcmapping.xml b/Diary/CoreData/Model.xcmappingmodel/xcmapping.xml similarity index 100% rename from Diary/Resource/Model.xcmappingmodel/xcmapping.xml rename to Diary/CoreData/Model.xcmappingmodel/xcmapping.xml diff --git a/Diary/Model/Location/LocationManager.swift b/Diary/Location/LocationManager.swift similarity index 65% rename from Diary/Model/Location/LocationManager.swift rename to Diary/Location/LocationManager.swift index f43b0ca24..a93c0a427 100644 --- a/Diary/Model/Location/LocationManager.swift +++ b/Diary/Location/LocationManager.swift @@ -25,16 +25,15 @@ final class LocationManager: NSObject, CLLocationManagerDelegate { locationManager.startUpdatingLocation() } - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + func locationManager( + _ manager: CLLocationManager, + didUpdateLocations locations: [CLLocation] + ) { guard let location = locations.last else { return } - locationCompletion?(location) // 마지막으로 받은 위치 데이터 전달 - locationManager.stopUpdatingLocation() // 위치 업데이트 중지 - locationCompletion = nil // 완료 핸들러 제거 - } - - func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { - + locationCompletion?(location) + locationManager.stopUpdatingLocation() + locationCompletion = nil } } diff --git a/Diary/Model/Network/NetworkManager.swift b/Diary/Model/Network/NetworkManager.swift deleted file mode 100644 index bc6d2b8b0..000000000 --- a/Diary/Model/Network/NetworkManager.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// NetworkManager.swift -// Diary -// -// Created by 김민성 on 2023/09/14. -// - -import Foundation -import UIKit -import CoreLocation - -final class NetworkManager { - - static func fetchWeatherIcon(icon: String) async -> UIImage? { - let url = URL(string: "https://openweathermap.org/img/wn/\(icon)@2x.png")! - - do { - let (data, response) = try await URLSession.shared.data(from: url) - - guard let httpResponse = response as? HTTPURLResponse else { - return nil - } - - guard (200..<300) ~= httpResponse.statusCode else { - return nil - } - - return UIImage(data: data) - - } catch { - return nil - } - } - - static func fetchCurrentWeather(coordinate: CLLocationCoordinate2D) async -> Weather? { - let url = "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinate.latitude)&lon=\(coordinate.longitude)&appid=888d5726542be62a76b7fabed4352d4a" - do { - let (data, response) = try await URLSession.shared.data(from: URL(string: url)!) - - guard let httpResponse = response as? HTTPURLResponse else { - return nil - } - - guard (200..<300) ~= httpResponse.statusCode else { - return nil - } - - return WeatherDecoder.decode(jsonData: data) - - } catch { - return nil - } - - } -} diff --git a/Diary/Model/JSONDecoder/WeatherDecoder.swift b/Diary/Model/Weather/Utils/WeatherDecoder.swift similarity index 71% rename from Diary/Model/JSONDecoder/WeatherDecoder.swift rename to Diary/Model/Weather/Utils/WeatherDecoder.swift index 390f6935f..fe8a71d9f 100644 --- a/Diary/Model/JSONDecoder/WeatherDecoder.swift +++ b/Diary/Model/Weather/Utils/WeatherDecoder.swift @@ -8,16 +8,14 @@ import Foundation struct WeatherDecoder { - static func decode(jsonData: Data) -> Weather? { + static func decode(jsonData: Data) throws -> Weather? { do { let decoder = JSONDecoder() let weatherData = try decoder.decode(WeatherData.self, from: jsonData) return weatherData.weather.first - } catch { - print("JSON 디코딩 에러: \(error)") - return nil + throw WeatherDecodingError.decodeFail } } } diff --git a/Diary/Model/Weather/Utils/WeatherDecodingError.swift b/Diary/Model/Weather/Utils/WeatherDecodingError.swift new file mode 100644 index 000000000..30145051e --- /dev/null +++ b/Diary/Model/Weather/Utils/WeatherDecodingError.swift @@ -0,0 +1,12 @@ +// +// WeatherDecodingError.swift +// Diary +// +// Created by 김민성 on 2023/09/15. +// + +import Foundation + +enum WeatherDecodingError: Error { + case decodeFail +} diff --git a/Diary/Model/Weather/Weather.swift b/Diary/Model/Weather/Weather.swift index f1b99b3ba..e541dc243 100644 --- a/Diary/Model/Weather/Weather.swift +++ b/Diary/Model/Weather/Weather.swift @@ -10,8 +10,5 @@ struct WeatherData: Decodable { } struct Weather: Decodable { - let id: Int - let main: String - let description: String let icon: String } diff --git a/Diary/Network/NetworkError.swift b/Diary/Network/NetworkError.swift new file mode 100644 index 000000000..3d689a813 --- /dev/null +++ b/Diary/Network/NetworkError.swift @@ -0,0 +1,15 @@ +// +// NetworkError.swift +// Diary +// +// Created by 김민성 on 2023/09/15. +// + +import Foundation + +enum NetworkError: Error { + case invalidIcon + case invalidURL + case invalidHTTPResponse + case badStatusCode +} diff --git a/Diary/Network/NetworkManager.swift b/Diary/Network/NetworkManager.swift new file mode 100644 index 000000000..61c522ac8 --- /dev/null +++ b/Diary/Network/NetworkManager.swift @@ -0,0 +1,65 @@ +// +// NetworkManager.swift +// Diary +// +// Created by 김민성 on 2023/09/14. +// + +import Foundation +import UIKit +import CoreLocation + +final class NetworkManager { + static let iconURL = "https://openweathermap.org/img/wn/" + static let currentWeatherURL = "https://api.openweathermap.org/data/2.5/weather" + + static func fetchWeatherIcon(icon: String?) async throws -> Data { + guard let icon = icon else { + throw NetworkError.invalidIcon + } + + let url = URL(string: iconURL + "\(icon)@2x.png") + + guard let url = url else { + throw NetworkError.invalidURL + } + + let (data, response) = try await URLSession.shared.data(from: url) + + guard let httpResponse = response as? HTTPURLResponse else { + throw NetworkError.invalidHTTPResponse + } + + guard (200..<300) ~= httpResponse.statusCode else { + throw NetworkError.badStatusCode + } + + return data + + } + + static func fetchCurrentWeather(coordinate: CLLocationCoordinate2D) async throws -> Weather? { + var urlComponents = URLComponents(string: currentWeatherURL) + let lat = URLQueryItem(name: "lat", value: coordinate.latitude.description) + let lon = URLQueryItem(name: "lon", value: coordinate.longitude.description) + let appid = URLQueryItem(name: "appid", value: "888d5726542be62a76b7fabed4352d4a") + + urlComponents?.queryItems = [lat, lon, appid] + + guard let url = urlComponents?.url else { + throw NetworkError.invalidURL + } + + let (data, response) = try await URLSession.shared.data(from: url) + + guard let httpResponse = response as? HTTPURLResponse else { + throw NetworkError.invalidHTTPResponse + } + + guard (200..<300) ~= httpResponse.statusCode else { + throw NetworkError.badStatusCode + } + + return try WeatherDecoder.decode(jsonData: data) + } +} diff --git a/Diary/View/DiaryCell.swift b/Diary/View/DiaryCell.swift index b381b0154..eee5a7260 100644 --- a/Diary/View/DiaryCell.swift +++ b/Diary/View/DiaryCell.swift @@ -32,7 +32,7 @@ final class DiaryCell: UICollectionViewListCell { let stackView = UIStackView() stackView.axis = .horizontal stackView.distribution = .fill - stackView.spacing = 24 + stackView.spacing = 12 return stackView }() @@ -65,19 +65,19 @@ final class DiaryCell: UICollectionViewListCell { return label }() - private lazy var weatherIcon: UIImageView = { - let image = UIImageView() - image.contentMode = .scaleAspectFit - return image + private lazy var weatherImage: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + return imageView }() - // MARK: - Internal Method func configureCell(diary: Diary, formatter: DateFormattable) { initFormatter(formatter: formatter) addSubviews() configureLabel(from: diary) + configureWeatherImage(from: diary) constraintOuterStackView() constraintWeatherIcon() } @@ -93,20 +93,21 @@ final class DiaryCell: UICollectionViewListCell { verticalStackView.addArrangedSubview(titleLabel) verticalStackView.addArrangedSubview(horizontalStackView) horizontalStackView.addArrangedSubview(createdDateLabel) - horizontalStackView.addArrangedSubview(weatherIcon) + horizontalStackView.addArrangedSubview(weatherImage) horizontalStackView.addArrangedSubview(contentLabel) - accessories = [.disclosureIndicator()] } + private func configureWeatherImage(from diary: Diary) { + if let pngData = diary.weatherImage { + weatherImage.image = UIImage(data: pngData) + } + } + private func configureLabel(from diary: Diary) { titleLabel.text = diary.title contentLabel.text = diary.content createdDateLabel.text = currentFormatter?.format(date: diary.createdDate ?? Date(), style: .long) - Task { - weatherIcon.image = await NetworkManager.fetchWeatherIcon(icon: diary.weather ?? "10d") - } - } private func constraintOuterStackView() { @@ -120,8 +121,8 @@ final class DiaryCell: UICollectionViewListCell { private func constraintWeatherIcon() { NSLayoutConstraint.activate([ - weatherIcon.heightAnchor.constraint(equalToConstant: 24.0), - weatherIcon.widthAnchor.constraint(equalToConstant: 24.0) + weatherImage.heightAnchor.constraint(equalToConstant: 24.0), + weatherImage.widthAnchor.constraint(equalToConstant: 24.0) ]) } }