From 15d047f223bc5c5fb6e1356e8ae11ea5f4e7d51a Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Tue, 12 Mar 2024 15:17:17 +0900 Subject: [PATCH 01/21] =?UTF-8?q?Step1:=20=ED=8F=B4=EB=8D=94=EB=A7=81=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 60 +++++++++++++++++-- .../{ => Resource}/AppDelegate.swift | 0 .../{ => Resource}/SceneDelegate.swift | 0 .../{ => Source/Model}/Weather.swift | 0 .../Scene/Weather}/ViewController.swift | 0 .../WeatherDetailViewController.swift | 0 .../WeatherDetail}/WeatherTableViewCell.swift | 0 7 files changed, 54 insertions(+), 6 deletions(-) rename WeatherForecast/WeatherForecast/{ => Resource}/AppDelegate.swift (100%) rename WeatherForecast/WeatherForecast/{ => Resource}/SceneDelegate.swift (100%) rename WeatherForecast/WeatherForecast/{ => Source/Model}/Weather.swift (100%) rename WeatherForecast/WeatherForecast/{ => Source/Scene/Weather}/ViewController.swift (100%) rename WeatherForecast/WeatherForecast/{ => Source/Scene/WeatherDetail}/WeatherDetailViewController.swift (100%) rename WeatherForecast/WeatherForecast/{ => Source/Scene/WeatherDetail}/WeatherTableViewCell.swift (100%) diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index 9b2f170..fe232e2 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -43,6 +43,58 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 60EC0E772BA02A2B0042C815 /* Scene */ = { + isa = PBXGroup; + children = ( + 60EC0E792BA02A540042C815 /* WeatherDetail */, + 60EC0E782BA02A3C0042C815 /* Weather */, + ); + path = Scene; + sourceTree = ""; + }; + 60EC0E782BA02A3C0042C815 /* Weather */ = { + isa = PBXGroup; + children = ( + C7743D902B21C38100DF0D09 /* ViewController.swift */, + ); + path = Weather; + sourceTree = ""; + }; + 60EC0E792BA02A540042C815 /* WeatherDetail */ = { + isa = PBXGroup; + children = ( + C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, + C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, + ); + path = WeatherDetail; + sourceTree = ""; + }; + 60EC0E7A2BA02A820042C815 /* Resource */ = { + isa = PBXGroup; + children = ( + C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */, + C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */, + ); + path = Resource; + sourceTree = ""; + }; + 60EC0E7B2BA02A8C0042C815 /* Source */ = { + isa = PBXGroup; + children = ( + 60EC0E7C2BA02AC80042C815 /* Model */, + 60EC0E772BA02A2B0042C815 /* Scene */, + ); + path = Source; + sourceTree = ""; + }; + 60EC0E7C2BA02AC80042C815 /* Model */ = { + isa = PBXGroup; + children = ( + C741F66F2B58F00500A4DDC0 /* Weather.swift */, + ); + path = Model; + sourceTree = ""; + }; C7743D802B21C38100DF0D09 = { isa = PBXGroup; children = ( @@ -62,12 +114,8 @@ C7743D8B2B21C38100DF0D09 /* WeatherForecast */ = { isa = PBXGroup; children = ( - C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */, - C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */, - C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, - C7743D902B21C38100DF0D09 /* ViewController.swift */, - C741F66F2B58F00500A4DDC0 /* Weather.swift */, - C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, + 60EC0E7A2BA02A820042C815 /* Resource */, + 60EC0E7B2BA02A8C0042C815 /* Source */, C7743D922B21C38100DF0D09 /* Main.storyboard */, C7743D952B21C38200DF0D09 /* Assets.xcassets */, C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */, diff --git a/WeatherForecast/WeatherForecast/AppDelegate.swift b/WeatherForecast/WeatherForecast/Resource/AppDelegate.swift similarity index 100% rename from WeatherForecast/WeatherForecast/AppDelegate.swift rename to WeatherForecast/WeatherForecast/Resource/AppDelegate.swift diff --git a/WeatherForecast/WeatherForecast/SceneDelegate.swift b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift similarity index 100% rename from WeatherForecast/WeatherForecast/SceneDelegate.swift rename to WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift diff --git a/WeatherForecast/WeatherForecast/Weather.swift b/WeatherForecast/WeatherForecast/Source/Model/Weather.swift similarity index 100% rename from WeatherForecast/WeatherForecast/Weather.swift rename to WeatherForecast/WeatherForecast/Source/Model/Weather.swift diff --git a/WeatherForecast/WeatherForecast/ViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/ViewController.swift similarity index 100% rename from WeatherForecast/WeatherForecast/ViewController.swift rename to WeatherForecast/WeatherForecast/Source/Scene/Weather/ViewController.swift diff --git a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift similarity index 100% rename from WeatherForecast/WeatherForecast/WeatherDetailViewController.swift rename to WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift diff --git a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherTableViewCell.swift similarity index 100% rename from WeatherForecast/WeatherForecast/WeatherTableViewCell.swift rename to WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherTableViewCell.swift From 9fb6d1364b67c0d1467d734e3c6a627f78a558fb Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Tue, 12 Mar 2024 15:27:14 +0900 Subject: [PATCH 02/21] =?UTF-8?q?Step1:=20=EC=8A=A4=ED=86=A0=EB=A6=AC?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 24 ++------- .../Base.lproj/Main.storyboard | 51 ------------------- WeatherForecast/WeatherForecast/Info.plist | 2 - .../Resource/SceneDelegate.swift | 43 +++------------- .../WeatherTableViewCell.swift | 0 ...ller.swift => WeatherViewController.swift} | 11 ++-- 6 files changed, 19 insertions(+), 112 deletions(-) delete mode 100644 WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard rename WeatherForecast/WeatherForecast/Source/Scene/{WeatherDetail => Weather}/WeatherTableViewCell.swift (100%) rename WeatherForecast/WeatherForecast/Source/Scene/Weather/{ViewController.swift => WeatherViewController.swift} (95%) diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index fe232e2..4a91de2 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -10,8 +10,7 @@ C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = C741F66F2B58F00500A4DDC0 /* Weather.swift */; }; C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */; }; C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */; }; - C7743D912B21C38100DF0D09 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D902B21C38100DF0D09 /* ViewController.swift */; }; - C7743D942B21C38100DF0D09 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7743D922B21C38100DF0D09 /* Main.storyboard */; }; + C7743D912B21C38100DF0D09 /* WeatherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D902B21C38100DF0D09 /* WeatherViewController.swift */; }; C7743D962B21C38200DF0D09 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C7743D952B21C38200DF0D09 /* Assets.xcassets */; }; C7743D992B21C38200DF0D09 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */; }; C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */; }; @@ -23,8 +22,7 @@ C7743D892B21C38100DF0D09 /* WeatherForecast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WeatherForecast.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - C7743D902B21C38100DF0D09 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - C7743D932B21C38100DF0D09 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + C7743D902B21C38100DF0D09 /* WeatherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewController.swift; sourceTree = ""; }; C7743D952B21C38200DF0D09 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C7743D982B21C38200DF0D09 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C7743D9A2B21C38200DF0D09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -55,7 +53,8 @@ 60EC0E782BA02A3C0042C815 /* Weather */ = { isa = PBXGroup; children = ( - C7743D902B21C38100DF0D09 /* ViewController.swift */, + C7743D902B21C38100DF0D09 /* WeatherViewController.swift */, + C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, ); path = Weather; sourceTree = ""; @@ -63,7 +62,6 @@ 60EC0E792BA02A540042C815 /* WeatherDetail */ = { isa = PBXGroup; children = ( - C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, ); path = WeatherDetail; @@ -116,7 +114,6 @@ children = ( 60EC0E7A2BA02A820042C815 /* Resource */, 60EC0E7B2BA02A8C0042C815 /* Source */, - C7743D922B21C38100DF0D09 /* Main.storyboard */, C7743D952B21C38200DF0D09 /* Assets.xcassets */, C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */, C7743D9A2B21C38200DF0D09 /* Info.plist */, @@ -184,7 +181,6 @@ files = ( C7743D992B21C38200DF0D09 /* LaunchScreen.storyboard in Resources */, C7743D962B21C38200DF0D09 /* Assets.xcassets in Resources */, - C7743D942B21C38100DF0D09 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -196,7 +192,7 @@ buildActionMask = 2147483647; files = ( C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, - C7743D912B21C38100DF0D09 /* ViewController.swift in Sources */, + C7743D912B21C38100DF0D09 /* WeatherViewController.swift in Sources */, C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, @@ -207,14 +203,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - C7743D922B21C38100DF0D09 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - C7743D932B21C38100DF0D09 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -356,7 +344,6 @@ INFOPLIST_FILE = WeatherForecast/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; LD_RUNPATH_SEARCH_PATHS = ( @@ -383,7 +370,6 @@ INFOPLIST_FILE = WeatherForecast/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard b/WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard deleted file mode 100644 index 4798dc7..0000000 --- a/WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WeatherForecast/WeatherForecast/Info.plist b/WeatherForecast/WeatherForecast/Info.plist index dd3c9af..0eb786d 100644 --- a/WeatherForecast/WeatherForecast/Info.plist +++ b/WeatherForecast/WeatherForecast/Info.plist @@ -15,8 +15,6 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift index 264a1ab..d6a0a65 100644 --- a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift +++ b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift @@ -10,42 +10,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). + + guard let scene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: scene) + + let viewController = WeatherViewController() + let nvc = UINavigationController(rootViewController: viewController) + window?.rootViewController = nvc + window?.makeKeyAndVisible() } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } - - } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift similarity index 100% rename from WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherTableViewCell.swift rename to WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/ViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift similarity index 95% rename from WeatherForecast/WeatherForecast/Source/Scene/Weather/ViewController.swift rename to WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 50b66fb..111fc37 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/ViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -6,7 +6,7 @@ import UIKit -class ViewController: UIViewController { +class WeatherViewController: UIViewController { var tableView: UITableView! let refreshControl: UIRefreshControl = UIRefreshControl() var weatherJSON: WeatherJSON? @@ -27,7 +27,7 @@ class ViewController: UIViewController { } } -extension ViewController { +extension WeatherViewController { @objc private func changeTempUnit() { switch tempUnit { case .imperial: @@ -47,6 +47,7 @@ extension ViewController { } private func initialSetUp() { + view.backgroundColor = .systemBackground navigationItem.rightBarButtonItem = UIBarButtonItem(title: "화씨", image: nil, target: self, action: #selector(changeTempUnit)) layTable() @@ -77,7 +78,7 @@ extension ViewController { } } -extension ViewController { +extension WeatherViewController { private func fetchWeatherJSON() { let jsonDecoder: JSONDecoder = .init() @@ -100,7 +101,7 @@ extension ViewController { } } -extension ViewController: UITableViewDataSource { +extension WeatherViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { 1 @@ -151,7 +152,7 @@ extension ViewController: UITableViewDataSource { } } -extension ViewController: UITableViewDelegate { +extension WeatherViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) From f7f07ab4a3630b2925c9740f4138db2488bdc146 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Tue, 12 Mar 2024 16:18:56 +0900 Subject: [PATCH 03/21] =?UTF-8?q?Step1:=20DateFormatter=20=EC=8B=B1?= =?UTF-8?q?=EA=B8=80=ED=86=A4=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 20 ++++++++++++ .../Resource/SceneDelegate.swift | 4 +-- .../Scene/Weather/WeatherViewController.swift | 9 +----- .../WeatherDetailViewController.swift | 22 +++++-------- .../Utils/Extensions/DateFormatter+.swift | 31 +++++++++++++++++++ 5 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 WeatherForecast/WeatherForecast/Source/Utils/Extensions/DateFormatter+.swift diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index 4a91de2..60c6360 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 60EC0E812BA032760042C815 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E802BA032760042C815 /* DateFormatter+.swift */; }; C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = C741F66F2B58F00500A4DDC0 /* Weather.swift */; }; C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */; }; C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */; }; @@ -18,6 +19,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 60EC0E802BA032760042C815 /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; C741F66F2B58F00500A4DDC0 /* Weather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weather.swift; sourceTree = ""; }; C7743D892B21C38100DF0D09 /* WeatherForecast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WeatherForecast.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -79,6 +81,7 @@ 60EC0E7B2BA02A8C0042C815 /* Source */ = { isa = PBXGroup; children = ( + 60EC0E7F2BA032660042C815 /* Utils */, 60EC0E7C2BA02AC80042C815 /* Model */, 60EC0E772BA02A2B0042C815 /* Scene */, ); @@ -93,6 +96,22 @@ path = Model; sourceTree = ""; }; + 60EC0E7F2BA032660042C815 /* Utils */ = { + isa = PBXGroup; + children = ( + 60EC0E822BA0327B0042C815 /* Extensions */, + ); + path = Utils; + sourceTree = ""; + }; + 60EC0E822BA0327B0042C815 /* Extensions */ = { + isa = PBXGroup; + children = ( + 60EC0E802BA032760042C815 /* DateFormatter+.swift */, + ); + path = Extensions; + sourceTree = ""; + }; C7743D802B21C38100DF0D09 = { isa = PBXGroup; children = ( @@ -194,6 +213,7 @@ C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, C7743D912B21C38100DF0D09 /* WeatherViewController.swift in Sources */, C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, + 60EC0E812BA032760042C815 /* DateFormatter+.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */, diff --git a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift index d6a0a65..32c8cce 100644 --- a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift +++ b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift @@ -16,8 +16,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: scene) let viewController = WeatherViewController() - let nvc = UINavigationController(rootViewController: viewController) - window?.rootViewController = nvc + let navigationController = UINavigationController(rootViewController: viewController) + window?.rootViewController = navigationController window?.makeKeyAndVisible() } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 111fc37..b38d7ff 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -12,13 +12,6 @@ class WeatherViewController: UIViewController { var weatherJSON: WeatherJSON? var icons: [UIImage]? let imageChache: NSCache = NSCache() - let dateFormatter: DateFormatter = { - let formatter: DateFormatter = DateFormatter() - formatter.locale = .init(identifier: "ko_KR") - formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm" - return formatter - }() - var tempUnit: TempUnit = .metric override func viewDidLoad() { @@ -124,7 +117,7 @@ extension WeatherViewController: UITableViewDataSource { cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.expression)" let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) - cell.dateLabel.text = dateFormatter.string(from: date) + cell.dateLabel.text = DateFormatter.convertToKorean(by: date) let iconName: String = weatherForecastInfo.weather.icon let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift index 69d3dfb..900d08e 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift @@ -11,14 +11,7 @@ class WeatherDetailViewController: UIViewController { var weatherForecastInfo: WeatherForecastInfo? var cityInfo: City? var tempUnit: TempUnit = .metric - - let dateFormatter: DateFormatter = { - let formatter: DateFormatter = DateFormatter() - formatter.locale = .init(identifier: "ko_KR") - formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm" - return formatter - }() - + override func viewDidLoad() { super.viewDidLoad() initialSetUp() @@ -30,7 +23,7 @@ class WeatherDetailViewController: UIViewController { guard let listInfo = weatherForecastInfo else { return } let date: Date = Date(timeIntervalSince1970: listInfo.dt) - navigationItem.title = dateFormatter.string(from: date) + navigationItem.title = DateFormatter.convertToKorean(by: date) let iconImageView: UIImageView = UIImageView() let weatherGroupLabel: UILabel = UILabel() @@ -103,12 +96,11 @@ class WeatherDetailViewController: UIViewController { humidityLabel.text = "습도 : \(listInfo.main.humidity)%" if let cityInfo { - let formatter: DateFormatter = DateFormatter() - formatter.dateFormat = .none - formatter.timeStyle = .short - formatter.locale = .init(identifier: "ko_KR") - sunriseTimeLabel.text = "일출 : \(formatter.string(from: Date(timeIntervalSince1970: cityInfo.sunrise)))" - sunsetTimeLabel.text = "일몰 : \(formatter.string(from: Date(timeIntervalSince1970: cityInfo.sunset)))" + let sunriseDate = Date(timeIntervalSince1970: cityInfo.sunrise) + let sunsetDate = Date(timeIntervalSince1970: cityInfo.sunset) + + sunriseTimeLabel.text = "일출 : \(DateFormatter.convertToCityTime(by: sunriseDate))" + sunsetTimeLabel.text = "일몰 : \(DateFormatter.convertToCityTime(by: sunsetDate))" } Task { diff --git a/WeatherForecast/WeatherForecast/Source/Utils/Extensions/DateFormatter+.swift b/WeatherForecast/WeatherForecast/Source/Utils/Extensions/DateFormatter+.swift new file mode 100644 index 0000000..11b0be7 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Utils/Extensions/DateFormatter+.swift @@ -0,0 +1,31 @@ +// +// DateFormatter+.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/12. +// + +import Foundation + +extension DateFormatter { + static func convertToKorean(by date: Date) -> String { + let formatter: DateFormatter = DateFormatter() + formatter.locale = .init(identifier: "ko_KR") + formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm" + + let time = formatter.string(from: date) + + return time + } + + static func convertToCityTime(by date: Date) -> String { + let formatter: DateFormatter = DateFormatter() + formatter.dateFormat = .none + formatter.timeStyle = .short + formatter.locale = .init(identifier: "ko_KR") + + let time = formatter.string(from: date) + + return time + } +} From dcdb3133cc3a3656e36560a7d742fc5171941c23 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 11:14:53 +0900 Subject: [PATCH 04/21] =?UTF-8?q?Step1:=20=EB=AA=A8=EB=8D=B8=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Model/TempUnit.swift | 18 ++++++++ .../Source/Model/Weather.swift | 46 ++++++++++--------- .../Source/Model/WeatherJSON.swift | 12 +++++ .../Scene/Weather/WeatherViewModel.swift | 8 ++++ .../Source/Service/WeatherService.swift | 8 ++++ 5 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift create mode 100644 WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift create mode 100644 WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift create mode 100644 WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift diff --git a/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift b/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift new file mode 100644 index 0000000..5b6d4ad --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift @@ -0,0 +1,18 @@ +// +// TempUnit.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/12. +// + + +// MARK: - Temperature Unit +enum TempUnit: String { + case metric, imperial + var expression: String { + switch self { + case .metric: return "℃" + case .imperial: return "℉" + } + } +} diff --git a/WeatherForecast/WeatherForecast/Source/Model/Weather.swift b/WeatherForecast/WeatherForecast/Source/Model/Weather.swift index ede7585..3ae3ca4 100644 --- a/WeatherForecast/WeatherForecast/Source/Model/Weather.swift +++ b/WeatherForecast/WeatherForecast/Source/Model/Weather.swift @@ -6,14 +6,8 @@ import Foundation -// MARK: - Weather JSON Format -class WeatherJSON: Decodable { - let weatherForecast: [WeatherForecastInfo] - let city: City -} - // MARK: - List -class WeatherForecastInfo: Decodable { +struct WeatherForecastInfo: Decodable { let dt: TimeInterval let main: MainInfo let weather: Weather @@ -21,13 +15,13 @@ class WeatherForecastInfo: Decodable { } // MARK: - MainClass -class MainInfo: Decodable { +struct MainInfo: Decodable { let temp, feelsLike, tempMin, tempMax: Double let pressure, seaLevel, grndLevel, humidity, pop: Double } // MARK: - Weather -class Weather: Decodable { +struct Weather: Decodable { let id: Int let main: String let description: String @@ -35,28 +29,36 @@ class Weather: Decodable { } // MARK: - City -class City: Decodable { +struct City: Decodable { let id: Int let name: String let coord: Coord let country: String let population, timezone: Int let sunrise, sunset: TimeInterval + + init(id: Int, name: String, coord: Coord, country: String, population: Int, timezone: Int, sunrise: TimeInterval, sunset: TimeInterval) { + self.id = id + self.name = name + self.coord = coord + self.country = country + self.population = population + self.timezone = timezone + self.sunrise = sunrise + self.sunset = sunset + } + + static let mock: City = City(id: 0, name: "", coord: Coord.mock, country: "", population: 0, timezone: 0, sunrise: 0, sunset: 0) } // MARK: - Coord -class Coord: Decodable { +struct Coord: Decodable { let lat, lon: Double -} - -// MARK: - Temperature Unit -enum TempUnit: String { - case metric, imperial - var expression: String { - switch self { - case .metric: return "℃" - case .imperial: return "℉" - } + + init(lat: Double, lon: Double) { + self.lat = lat + self.lon = lon } + + static let mock: Coord = Coord(lat: 0, lon: 0) } - diff --git a/WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift b/WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift new file mode 100644 index 0000000..d8e88e4 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift @@ -0,0 +1,12 @@ +// +// WeatherJSON.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/12. +// + +// MARK: - Weather JSON Format +struct WeatherJSON: Decodable { + let weatherForecast: [WeatherForecastInfo] + let city: City +} diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift new file mode 100644 index 0000000..22306b4 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift @@ -0,0 +1,8 @@ +// +// WeatherViewModel.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/12. +// + +import Foundation diff --git a/WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift b/WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift new file mode 100644 index 0000000..f273ad1 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift @@ -0,0 +1,8 @@ +// +// WeatherService.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/12. +// + +import Foundation From 2f354d08a6e3a161eb0e8a46e06f6262fc867159 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 11:25:33 +0900 Subject: [PATCH 05/21] =?UTF-8?q?Step1:=20ViewController=EC=9D=98=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 24 ++++++ .../Resource/SceneDelegate.swift | 4 +- .../Scene/Weather/WeatherViewController.swift | 83 ++++++++----------- .../Scene/Weather/WeatherViewModel.swift | 68 +++++++++++++++ .../Source/Service/WeatherJSONService.swift | 31 +++++++ .../Source/Service/WeatherService.swift | 8 -- 6 files changed, 162 insertions(+), 56 deletions(-) create mode 100644 WeatherForecast/WeatherForecast/Source/Service/WeatherJSONService.swift delete mode 100644 WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index 60c6360..8e19fc7 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -8,6 +8,10 @@ /* Begin PBXBuildFile section */ 60EC0E812BA032760042C815 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E802BA032760042C815 /* DateFormatter+.swift */; }; + 60EC0E852BA0431A0042C815 /* WeatherJSONService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E842BA0431A0042C815 /* WeatherJSONService.swift */; }; + 60EC0E872BA044340042C815 /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E862BA044340042C815 /* WeatherViewModel.swift */; }; + 60EC0E892BA0673F0042C815 /* TempUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E882BA0673F0042C815 /* TempUnit.swift */; }; + 60EC0E8B2BA087720042C815 /* WeatherJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */; }; C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = C741F66F2B58F00500A4DDC0 /* Weather.swift */; }; C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */; }; C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */; }; @@ -20,6 +24,10 @@ /* Begin PBXFileReference section */ 60EC0E802BA032760042C815 /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; + 60EC0E842BA0431A0042C815 /* WeatherJSONService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherJSONService.swift; sourceTree = ""; }; + 60EC0E862BA044340042C815 /* WeatherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewModel.swift; sourceTree = ""; }; + 60EC0E882BA0673F0042C815 /* TempUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempUnit.swift; sourceTree = ""; }; + 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherJSON.swift; sourceTree = ""; }; C741F66F2B58F00500A4DDC0 /* Weather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weather.swift; sourceTree = ""; }; C7743D892B21C38100DF0D09 /* WeatherForecast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WeatherForecast.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -56,6 +64,7 @@ isa = PBXGroup; children = ( C7743D902B21C38100DF0D09 /* WeatherViewController.swift */, + 60EC0E862BA044340042C815 /* WeatherViewModel.swift */, C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, ); path = Weather; @@ -81,6 +90,7 @@ 60EC0E7B2BA02A8C0042C815 /* Source */ = { isa = PBXGroup; children = ( + 60EC0E832BA041390042C815 /* Service */, 60EC0E7F2BA032660042C815 /* Utils */, 60EC0E7C2BA02AC80042C815 /* Model */, 60EC0E772BA02A2B0042C815 /* Scene */, @@ -92,6 +102,8 @@ isa = PBXGroup; children = ( C741F66F2B58F00500A4DDC0 /* Weather.swift */, + 60EC0E882BA0673F0042C815 /* TempUnit.swift */, + 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */, ); path = Model; sourceTree = ""; @@ -112,6 +124,14 @@ path = Extensions; sourceTree = ""; }; + 60EC0E832BA041390042C815 /* Service */ = { + isa = PBXGroup; + children = ( + 60EC0E842BA0431A0042C815 /* WeatherJSONService.swift */, + ); + path = Service; + sourceTree = ""; + }; C7743D802B21C38100DF0D09 = { isa = PBXGroup; children = ( @@ -210,12 +230,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 60EC0E852BA0431A0042C815 /* WeatherJSONService.swift in Sources */, + 60EC0E872BA044340042C815 /* WeatherViewModel.swift in Sources */, C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, + 60EC0E892BA0673F0042C815 /* TempUnit.swift in Sources */, C7743D912B21C38100DF0D09 /* WeatherViewController.swift in Sources */, C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, 60EC0E812BA032760042C815 /* DateFormatter+.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, + 60EC0E8B2BA087720042C815 /* WeatherJSON.swift in Sources */, C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift index 32c8cce..368c0b2 100644 --- a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift +++ b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift @@ -15,7 +15,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let scene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: scene) - let viewController = WeatherViewController() + let weatherService: WeatherJSONService = WeatherJSONService() + let viewModel = WeatherViewModelImp(weatherService: weatherService) + let viewController = WeatherViewController(viewModel: viewModel) let navigationController = UINavigationController(rootViewController: viewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index b38d7ff..51cc5e0 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -7,34 +7,42 @@ import UIKit class WeatherViewController: UIViewController { - var tableView: UITableView! + var tableView: UITableView = .init(frame: .zero, style: .plain) let refreshControl: UIRefreshControl = UIRefreshControl() - var weatherJSON: WeatherJSON? - var icons: [UIImage]? - let imageChache: NSCache = NSCache() - var tempUnit: TempUnit = .metric + + private let viewModel: WeatherViewModel + + init(viewModel: WeatherViewModel) { + self.viewModel = viewModel + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } override func viewDidLoad() { super.viewDidLoad() + initialSetUp() } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewModel.fetch() + } } extension WeatherViewController { @objc private func changeTempUnit() { - switch tempUnit { - case .imperial: - tempUnit = .metric - navigationItem.rightBarButtonItem?.title = "섭씨" - case .metric: - tempUnit = .imperial - navigationItem.rightBarButtonItem?.title = "화씨" - } + viewModel.changeTempUnit() refresh() } @objc private func refresh() { - fetchWeatherJSON() + viewModel.fetch() tableView.reloadData() refreshControl.endRefreshing() } @@ -56,7 +64,6 @@ extension WeatherViewController { } private func layTable() { - tableView = .init(frame: .zero, style: .plain) view.addSubview(tableView) tableView.translatesAutoresizingMaskIntoConstraints = false @@ -71,47 +78,26 @@ extension WeatherViewController { } } -extension WeatherViewController { - private func fetchWeatherJSON() { - - let jsonDecoder: JSONDecoder = .init() - jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase - - guard let data = NSDataAsset(name: "weather")?.data else { - return - } - - let info: WeatherJSON - do { - info = try jsonDecoder.decode(WeatherJSON.self, from: data) - } catch { - print(error.localizedDescription) - return - } - - weatherJSON = info - navigationItem.title = weatherJSON?.city.name - } -} - extension WeatherViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - 1 + return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - weatherJSON?.weatherForecast.count ?? 0 + return viewModel.weatherForecast.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "WeatherCell", for: indexPath) - guard let cell: WeatherTableViewCell = cell as? WeatherTableViewCell, - let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] else { + guard let cell: WeatherTableViewCell = cell as? WeatherTableViewCell else { return cell } + let weatherForecastInfo = viewModel.weatherForecast[indexPath.row] + let tempUnit = viewModel.tempUnit + cell.weatherLabel.text = weatherForecastInfo.weather.main cell.descriptionLabel.text = weatherForecastInfo.weather.description cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.expression)" @@ -122,7 +108,7 @@ extension WeatherViewController: UITableViewDataSource { let iconName: String = weatherForecastInfo.weather.icon let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" - if let image = imageChache.object(forKey: urlString as NSString) { + if let image = viewModel.getCachedImage(urlString: urlString) { cell.weatherIcon.image = image return cell } @@ -134,7 +120,7 @@ extension WeatherViewController: UITableViewDataSource { return } - imageChache.setObject(image, forKey: urlString as NSString) + viewModel.setCachedImage(image, urlString: urlString) if indexPath == tableView.indexPath(for: cell) { cell.weatherIcon.image = image @@ -149,10 +135,13 @@ extension WeatherViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + let weatherForecastInfo = viewModel.weatherForecast[indexPath.row] + let city = viewModel.city + let detailViewController: WeatherDetailViewController = WeatherDetailViewController() - detailViewController.weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] - detailViewController.cityInfo = weatherJSON?.city - detailViewController.tempUnit = tempUnit + detailViewController.weatherForecastInfo = weatherForecastInfo + detailViewController.cityInfo = city + detailViewController.tempUnit = viewModel.tempUnit navigationController?.show(detailViewController, sender: self) } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift index 22306b4..7156cd5 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift @@ -6,3 +6,71 @@ // import Foundation +import UIKit + +protocol WeatherViewModel { + var weatherForecast: [WeatherForecastInfo] { get } + var city: City { get } + var tempUnit: TempUnit { get } + + var navigationBarItemTitle: String { get } + + func fetch() + func changeTempUnit() + func getCachedImage(urlString key: String) -> UIImage? + func setCachedImage(_ image: UIImage, urlString key: String) +} + +final class WeatherViewModelImp: WeatherViewModel { + private(set) var weatherForecast: [WeatherForecastInfo] = [] + private(set) var city: City = City.mock + private(set) var tempUnit: TempUnit = .metric + + private let weatherService: WeatherJSONService + private let imageCache: NSCache + + lazy var navigationBarItemTitle: String = { + switch tempUnit { + case.imperial: return "화씨" + case .metric: return "섭씨" + } + }() + + init(weatherService: WeatherJSONService) { + self.weatherService = weatherService + + imageCache = NSCache() + } + + func fetch() { + weatherService.fetchWeather { [weak self] data in + guard let self = self else { return } + + self.weatherForecast = data.weatherForecast + self.city = data.city + } + + navigationBarItemTitle = tempUnit.expression + } + + func changeTempUnit() { + switch tempUnit { + case .metric: + tempUnit = .imperial + case .imperial: + tempUnit = .metric + } + + navigationBarItemTitle = tempUnit.expression + } + + func getCachedImage(urlString key: String) -> UIImage? { + return imageCache.object(forKey: key as NSString) + } + + func setCachedImage(_ image: UIImage, + urlString key: String) { + imageCache.setObject(image, + forKey: key as NSString) + } +} diff --git a/WeatherForecast/WeatherForecast/Source/Service/WeatherJSONService.swift b/WeatherForecast/WeatherForecast/Source/Service/WeatherJSONService.swift new file mode 100644 index 0000000..b17dfc9 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Service/WeatherJSONService.swift @@ -0,0 +1,31 @@ +// +// WeatherService.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/12. +// + +import Foundation +import UIKit + +final class WeatherJSONService { + func fetchWeather(completion: @escaping (WeatherJSON) -> ()) { + let jsonDecoder: JSONDecoder = .init() + jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase + + guard let data = NSDataAsset(name: "weather")?.data else { + return + } + + let info: WeatherJSON + + do { + info = try jsonDecoder.decode(WeatherJSON.self, from: data) + } catch { + print(error.localizedDescription) + return + } + + completion(info) + } +} diff --git a/WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift b/WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift deleted file mode 100644 index f273ad1..0000000 --- a/WeatherForecast/WeatherForecast/Source/Service/WeatherService.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// WeatherService.swift -// WeatherForecast -// -// Created by 홍승완 on 2024/03/12. -// - -import Foundation From 940321cc265bb7fc88275fbb5c4609521853a678 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 11:37:26 +0900 Subject: [PATCH 06/21] =?UTF-8?q?Step1:=20cell=20identifier=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Scene/Weather/WeatherTableViewCell.swift | 2 ++ .../Source/Scene/Weather/WeatherViewController.swift | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift index 42cb519..8c4a56c 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift @@ -7,6 +7,8 @@ import UIKit class WeatherTableViewCell: UITableViewCell { + static let identifier = String(describing: WeatherTableViewCell.self) + var weatherIcon: UIImageView! var dateLabel: UILabel! var temperatureLabel: UILabel! diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 51cc5e0..d25194a 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -58,7 +58,7 @@ extension WeatherViewController { for: .valueChanged) tableView.refreshControl = refreshControl - tableView.register(WeatherTableViewCell.self, forCellReuseIdentifier: "WeatherCell") + tableView.register(WeatherTableViewCell.self, forCellReuseIdentifier: WeatherTableViewCell.identifier) tableView.dataSource = self tableView.delegate = self } @@ -89,7 +89,7 @@ extension WeatherViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "WeatherCell", for: indexPath) + let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: WeatherTableViewCell.identifier, for: indexPath) guard let cell: WeatherTableViewCell = cell as? WeatherTableViewCell else { return cell From a5b648b980a74cc34982bd37e27c5770aa55bf51 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 11:44:56 +0900 Subject: [PATCH 07/21] =?UTF-8?q?Step1:=20TempUnit=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=AA=85=ED=99=95=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Model/TempUnit.swift | 20 +++++++++++++++---- .../Scene/Weather/WeatherViewController.swift | 2 +- .../Scene/Weather/WeatherViewModel.swift | 18 ++++++++--------- .../WeatherDetailViewController.swift | 10 +++++----- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift b/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift index 5b6d4ad..00ffc29 100644 --- a/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift +++ b/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift @@ -8,11 +8,23 @@ // MARK: - Temperature Unit enum TempUnit: String { - case metric, imperial - var expression: String { + case celsius, fahrenheit + + var symbol: String { switch self { - case .metric: return "℃" - case .imperial: return "℉" + case .celsius: + return "℃" + case .fahrenheit: + return "℉" + } + } + + var description: String { + switch self { + case .celsius: + return "섭씨" + case .fahrenheit: + return "화씨" } } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index d25194a..0486d9c 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -100,7 +100,7 @@ extension WeatherViewController: UITableViewDataSource { cell.weatherLabel.text = weatherForecastInfo.weather.main cell.descriptionLabel.text = weatherForecastInfo.weather.description - cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.expression)" + cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.symbol)" let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) cell.dateLabel.text = DateFormatter.convertToKorean(by: date) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift index 7156cd5..feb98ac 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift @@ -24,15 +24,15 @@ protocol WeatherViewModel { final class WeatherViewModelImp: WeatherViewModel { private(set) var weatherForecast: [WeatherForecastInfo] = [] private(set) var city: City = City.mock - private(set) var tempUnit: TempUnit = .metric + private(set) var tempUnit: TempUnit = .celsius private let weatherService: WeatherJSONService private let imageCache: NSCache lazy var navigationBarItemTitle: String = { switch tempUnit { - case.imperial: return "화씨" - case .metric: return "섭씨" + case.fahrenheit: return "화씨" + case .celsius: return "섭씨" } }() @@ -50,18 +50,18 @@ final class WeatherViewModelImp: WeatherViewModel { self.city = data.city } - navigationBarItemTitle = tempUnit.expression + navigationBarItemTitle = tempUnit.symbol } func changeTempUnit() { switch tempUnit { - case .metric: - tempUnit = .imperial - case .imperial: - tempUnit = .metric + case .celsius: + tempUnit = .fahrenheit + case .fahrenheit: + tempUnit = .celsius } - navigationBarItemTitle = tempUnit.expression + navigationBarItemTitle = tempUnit.symbol } func getCachedImage(urlString key: String) -> UIImage? { diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift index 900d08e..abde4f2 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift @@ -10,7 +10,7 @@ class WeatherDetailViewController: UIViewController { var weatherForecastInfo: WeatherForecastInfo? var cityInfo: City? - var tempUnit: TempUnit = .metric + var tempUnit: TempUnit = .celsius override func viewDidLoad() { super.viewDidLoad() @@ -88,10 +88,10 @@ class WeatherDetailViewController: UIViewController { weatherGroupLabel.text = listInfo.weather.main weatherDescriptionLabel.text = listInfo.weather.description - temperatureLabel.text = "현재 기온 : \(listInfo.main.temp)\(tempUnit.expression)" - feelsLikeLabel.text = "체감 기온 : \(listInfo.main.feelsLike)\(tempUnit.expression)" - maximumTemperatureLable.text = "최고 기온 : \(listInfo.main.tempMax)\(tempUnit.expression)" - minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(tempUnit.expression)" + temperatureLabel.text = "현재 기온 : \(listInfo.main.temp)\(tempUnit.symbol)" + feelsLikeLabel.text = "체감 기온 : \(listInfo.main.feelsLike)\(tempUnit.symbol)" + maximumTemperatureLable.text = "최고 기온 : \(listInfo.main.tempMax)\(tempUnit.symbol)" + minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(tempUnit.symbol)" popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" humidityLabel.text = "습도 : \(listInfo.main.humidity)%" From 94fb5cd9795e1e0d7780d53c6cfc240e38f1420c Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 20:10:58 +0900 Subject: [PATCH 08/21] =?UTF-8?q?Step1:=20tempUnit=EC=9D=98=20=EA=B0=92=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=9D=98=20=EC=B1=85=EC=9E=84=EC=9D=84=20?= =?UTF-8?q?=EC=9E=90=EC=8B=A0=EC=9D=B4=20=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resource/SceneDelegate.swift | 2 +- .../Source/Model/TempUnit.swift | 9 +++++++ .../Scene/Weather/WeatherViewController.swift | 7 ++++- .../Scene/Weather/WeatherViewModel.swift | 27 +++++-------------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift index 368c0b2..dff541b 100644 --- a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift +++ b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift @@ -16,7 +16,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: scene) let weatherService: WeatherJSONService = WeatherJSONService() - let viewModel = WeatherViewModelImp(weatherService: weatherService) + let viewModel = WeatherViewModelImp(weatherService: weatherService, tempUnit: TempUnit.fahrenheit) let viewController = WeatherViewController(viewModel: viewModel) let navigationController = UINavigationController(rootViewController: viewController) window?.rootViewController = navigationController diff --git a/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift b/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift index 00ffc29..bb129bd 100644 --- a/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift +++ b/WeatherForecast/WeatherForecast/Source/Model/TempUnit.swift @@ -27,4 +27,13 @@ enum TempUnit: String { return "화씨" } } + + mutating func toggle() { + switch self { + case .celsius: + self = .fahrenheit + case .fahrenheit: + self = .celsius + } + } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 0486d9c..fdb8bcb 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -45,11 +45,16 @@ extension WeatherViewController { viewModel.fetch() tableView.reloadData() refreshControl.endRefreshing() + navigationItem.rightBarButtonItem?.title = viewModel.navigationBarItemTitle + navigationItem.title = viewModel.city.name } private func initialSetUp() { view.backgroundColor = .systemBackground - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "화씨", image: nil, target: self, action: #selector(changeTempUnit)) + + let tempUnit = viewModel.tempUnit + navigationItem.rightBarButtonItem = UIBarButtonItem(title: tempUnit.description, image: nil, target: self, action: #selector(changeTempUnit)) + navigationItem.title = viewModel.city.name layTable() diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift index feb98ac..5867647 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift @@ -24,21 +24,17 @@ protocol WeatherViewModel { final class WeatherViewModelImp: WeatherViewModel { private(set) var weatherForecast: [WeatherForecastInfo] = [] private(set) var city: City = City.mock - private(set) var tempUnit: TempUnit = .celsius - + private(set) var tempUnit: TempUnit private let weatherService: WeatherJSONService private let imageCache: NSCache - lazy var navigationBarItemTitle: String = { - switch tempUnit { - case.fahrenheit: return "화씨" - case .celsius: return "섭씨" - } - }() + var navigationBarItemTitle: String { + return tempUnit.description + } - init(weatherService: WeatherJSONService) { + init(weatherService: WeatherJSONService, tempUnit: TempUnit) { self.weatherService = weatherService - + self.tempUnit = tempUnit imageCache = NSCache() } @@ -49,19 +45,10 @@ final class WeatherViewModelImp: WeatherViewModel { self.weatherForecast = data.weatherForecast self.city = data.city } - - navigationBarItemTitle = tempUnit.symbol } func changeTempUnit() { - switch tempUnit { - case .celsius: - tempUnit = .fahrenheit - case .fahrenheit: - tempUnit = .celsius - } - - navigationBarItemTitle = tempUnit.symbol + tempUnit.toggle() } func getCachedImage(urlString key: String) -> UIImage? { From f2576d2f36811b488612aceb985459a2de64547f Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 20:32:06 +0900 Subject: [PATCH 09/21] =?UTF-8?q?Step1:=20ImageServier=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Service/WeatherImageService.swift | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift diff --git a/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift b/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift new file mode 100644 index 0000000..6cea987 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift @@ -0,0 +1,8 @@ +// +// WeatherImageService.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/13. +// + +import Foundation From 6852563d038009933220d3dadf7ef51ea192032e Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 20:32:21 +0900 Subject: [PATCH 10/21] =?UTF-8?q?Step1:=20imageService=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 4 +++ .../Resource/SceneDelegate.swift | 5 +++- .../Scene/Weather/WeatherViewController.swift | 27 +++++-------------- .../Scene/Weather/WeatherViewModel.swift | 22 +++++++-------- .../Source/Service/WeatherImageService.swift | 26 +++++++++++++++++- 5 files changed, 49 insertions(+), 35 deletions(-) diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index 8e19fc7..cd27b5c 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 60EC0E872BA044340042C815 /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E862BA044340042C815 /* WeatherViewModel.swift */; }; 60EC0E892BA0673F0042C815 /* TempUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E882BA0673F0042C815 /* TempUnit.swift */; }; 60EC0E8B2BA087720042C815 /* WeatherJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */; }; + 60EC0E8F2BA1C2520042C815 /* WeatherImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E8E2BA1C2520042C815 /* WeatherImageService.swift */; }; C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = C741F66F2B58F00500A4DDC0 /* Weather.swift */; }; C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */; }; C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */; }; @@ -28,6 +29,7 @@ 60EC0E862BA044340042C815 /* WeatherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewModel.swift; sourceTree = ""; }; 60EC0E882BA0673F0042C815 /* TempUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempUnit.swift; sourceTree = ""; }; 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherJSON.swift; sourceTree = ""; }; + 60EC0E8E2BA1C2520042C815 /* WeatherImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherImageService.swift; sourceTree = ""; }; C741F66F2B58F00500A4DDC0 /* Weather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weather.swift; sourceTree = ""; }; C7743D892B21C38100DF0D09 /* WeatherForecast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WeatherForecast.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -128,6 +130,7 @@ isa = PBXGroup; children = ( 60EC0E842BA0431A0042C815 /* WeatherJSONService.swift */, + 60EC0E8E2BA1C2520042C815 /* WeatherImageService.swift */, ); path = Service; sourceTree = ""; @@ -238,6 +241,7 @@ C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, 60EC0E812BA032760042C815 /* DateFormatter+.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, + 60EC0E8F2BA1C2520042C815 /* WeatherImageService.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, 60EC0E8B2BA087720042C815 /* WeatherJSON.swift in Sources */, C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */, diff --git a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift index dff541b..dcb396c 100644 --- a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift +++ b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift @@ -16,7 +16,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: scene) let weatherService: WeatherJSONService = WeatherJSONService() - let viewModel = WeatherViewModelImp(weatherService: weatherService, tempUnit: TempUnit.fahrenheit) + let imageService: WeatherImageService = WeatherImageService() + let viewModel = WeatherViewModelImp(weatherService: weatherService, + imageService: imageService, + tempUnit: TempUnit.fahrenheit) let viewController = WeatherViewController(viewModel: viewModel) let navigationController = UINavigationController(rootViewController: viewController) window?.rootViewController = navigationController diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index fdb8bcb..3815e59 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -102,32 +102,17 @@ extension WeatherViewController: UITableViewDataSource { let weatherForecastInfo = viewModel.weatherForecast[indexPath.row] let tempUnit = viewModel.tempUnit + let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) + let iconName: String = weatherForecastInfo.weather.icon cell.weatherLabel.text = weatherForecastInfo.weather.main cell.descriptionLabel.text = weatherForecastInfo.weather.description cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.symbol)" - - let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) cell.dateLabel.text = DateFormatter.convertToKorean(by: date) - - let iconName: String = weatherForecastInfo.weather.icon - let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" - - if let image = viewModel.getCachedImage(urlString: urlString) { - cell.weatherIcon.image = image - return cell - } - - Task { - guard let url: URL = URL(string: urlString), - let (data, _) = try? await URLSession.shared.data(from: url), - let image: UIImage = UIImage(data: data) else { - return - } - - viewModel.setCachedImage(image, urlString: urlString) - - if indexPath == tableView.indexPath(for: cell) { + + viewModel.fetchImage(iconName: iconName) { [weak self] image in + guard let self = self else { return } + DispatchQueue.main.async { cell.weatherIcon.image = image } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift index 5867647..af65db7 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift @@ -12,13 +12,11 @@ protocol WeatherViewModel { var weatherForecast: [WeatherForecastInfo] { get } var city: City { get } var tempUnit: TempUnit { get } - var navigationBarItemTitle: String { get } func fetch() func changeTempUnit() - func getCachedImage(urlString key: String) -> UIImage? - func setCachedImage(_ image: UIImage, urlString key: String) + func fetchImage(iconName: String, completion: @escaping (UIImage) -> ()) } final class WeatherViewModelImp: WeatherViewModel { @@ -26,14 +24,18 @@ final class WeatherViewModelImp: WeatherViewModel { private(set) var city: City = City.mock private(set) var tempUnit: TempUnit private let weatherService: WeatherJSONService + private let imageService: WeatherImageService private let imageCache: NSCache var navigationBarItemTitle: String { return tempUnit.description } - init(weatherService: WeatherJSONService, tempUnit: TempUnit) { + init(weatherService: WeatherJSONService, + imageService: WeatherImageService, + tempUnit: TempUnit) { self.weatherService = weatherService + self.imageService = imageService self.tempUnit = tempUnit imageCache = NSCache() } @@ -51,13 +53,9 @@ final class WeatherViewModelImp: WeatherViewModel { tempUnit.toggle() } - func getCachedImage(urlString key: String) -> UIImage? { - return imageCache.object(forKey: key as NSString) - } - - func setCachedImage(_ image: UIImage, - urlString key: String) { - imageCache.setObject(image, - forKey: key as NSString) + func fetchImage(iconName: String, completion: @escaping (UIImage) -> ()) { + imageService.fetchImage(iconName: iconName) { image in + completion(image) + } } } diff --git a/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift b/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift index 6cea987..6c36dac 100644 --- a/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift +++ b/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift @@ -5,4 +5,28 @@ // Created by 홍승완 on 2024/03/13. // -import Foundation + +import UIKit + +final class WeatherImageService { + let imageCache: NSCache = NSCache() + + func fetchImage(iconName: String, completion: @escaping (UIImage) -> ()) { + let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" + + if let image = imageCache.object(forKey: urlString as NSString) { + completion(image) + } + + Task { + guard let url: URL = URL(string: urlString), + let (data, _) = try? await URLSession.shared.data(from: url), + let image: UIImage = UIImage(data: data) else { + return + } + + imageCache.setObject(image, forKey: urlString as NSString) + completion(image) + } + } +} From b88e253920b5f62e654e16331b876c21ac349c3d Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 20:40:27 +0900 Subject: [PATCH 11/21] =?UTF-8?q?Step1:=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90,=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=20?= =?UTF-8?q?=EA=B0=95=EC=A0=9C=20=EC=96=B8=EB=9E=98=ED=95=91=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scene/Weather/WeatherTableViewCell.swift | 18 ++++++------------ .../Scene/Weather/WeatherViewController.swift | 4 ++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift index 8c4a56c..dd71eaf 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift @@ -9,11 +9,12 @@ import UIKit class WeatherTableViewCell: UITableViewCell { static let identifier = String(describing: WeatherTableViewCell.self) - var weatherIcon: UIImageView! - var dateLabel: UILabel! - var temperatureLabel: UILabel! - var weatherLabel: UILabel! - var descriptionLabel: UILabel! + let weatherIcon: UIImageView = UIImageView() + let dateLabel: UILabel = UILabel() + let temperatureLabel: UILabel = UILabel() + let weatherLabel: UILabel = UILabel() + let descriptionLabel: UILabel = UILabel() + let dashLabel: UILabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -31,13 +32,6 @@ class WeatherTableViewCell: UITableViewCell { } private func layViews() { - weatherIcon = UIImageView() - dateLabel = UILabel() - temperatureLabel = UILabel() - weatherLabel = UILabel() - let dashLabel: UILabel = UILabel() - descriptionLabel = UILabel() - let labels: [UILabel] = [dateLabel, temperatureLabel, weatherLabel, dashLabel, descriptionLabel] labels.forEach { label in diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 3815e59..479696b 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -7,8 +7,8 @@ import UIKit class WeatherViewController: UIViewController { - var tableView: UITableView = .init(frame: .zero, style: .plain) - let refreshControl: UIRefreshControl = UIRefreshControl() + private let tableView: UITableView = .init(frame: .zero, style: .plain) + private let refreshControl: UIRefreshControl = UIRefreshControl() private let viewModel: WeatherViewModel From c0aae9c20eff0895c40b65b4375ccaeee5d2a5b8 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 20:41:10 +0900 Subject: [PATCH 12/21] =?UTF-8?q?Step1:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B3=80=EC=88=98=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift index af65db7..1b9f8b6 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift @@ -25,7 +25,6 @@ final class WeatherViewModelImp: WeatherViewModel { private(set) var tempUnit: TempUnit private let weatherService: WeatherJSONService private let imageService: WeatherImageService - private let imageCache: NSCache var navigationBarItemTitle: String { return tempUnit.description @@ -37,7 +36,6 @@ final class WeatherViewModelImp: WeatherViewModel { self.weatherService = weatherService self.imageService = imageService self.tempUnit = tempUnit - imageCache = NSCache() } func fetch() { From 7584f4bdf27cc618be9fb29bedb3a57f6e084380 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 20:47:02 +0900 Subject: [PATCH 13/21] =?UTF-8?q?Step1:=20DetailView=20=EC=9D=B8=EC=8A=A4?= =?UTF-8?q?=ED=84=B4=EC=8A=A4=20=EC=83=9D=EC=84=B1=EC=8B=9C=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scene/Weather/WeatherViewController.swift | 9 +++-- .../WeatherDetailViewController.swift | 37 ++++++++++++------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 479696b..9a1c93e 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -127,11 +127,12 @@ extension WeatherViewController: UITableViewDelegate { let weatherForecastInfo = viewModel.weatherForecast[indexPath.row] let city = viewModel.city + let tempUnit = viewModel.tempUnit + + let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherForecastInfo: weatherForecastInfo, + cityInfo: city, + tempUnit: tempUnit) - let detailViewController: WeatherDetailViewController = WeatherDetailViewController() - detailViewController.weatherForecastInfo = weatherForecastInfo - detailViewController.cityInfo = city - detailViewController.tempUnit = viewModel.tempUnit navigationController?.show(detailViewController, sender: self) } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift index abde4f2..c1f7c43 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift @@ -7,11 +7,21 @@ import UIKit class WeatherDetailViewController: UIViewController { - - var weatherForecastInfo: WeatherForecastInfo? - var cityInfo: City? - var tempUnit: TempUnit = .celsius - + private let weatherForecastInfo: WeatherForecastInfo + private let cityInfo: City + private let tempUnit: TempUnit + + init(weatherForecastInfo: WeatherForecastInfo, cityInfo: City, tempUnit: TempUnit) { + self.weatherForecastInfo = weatherForecastInfo + self.cityInfo = cityInfo + self.tempUnit = tempUnit + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } override func viewDidLoad() { super.viewDidLoad() initialSetUp() @@ -20,8 +30,7 @@ class WeatherDetailViewController: UIViewController { private func initialSetUp() { view.backgroundColor = .white - guard let listInfo = weatherForecastInfo else { return } - + let listInfo = weatherForecastInfo let date: Date = Date(timeIntervalSince1970: listInfo.dt) navigationItem.title = DateFormatter.convertToKorean(by: date) @@ -95,13 +104,13 @@ class WeatherDetailViewController: UIViewController { popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" humidityLabel.text = "습도 : \(listInfo.main.humidity)%" - if let cityInfo { - let sunriseDate = Date(timeIntervalSince1970: cityInfo.sunrise) - let sunsetDate = Date(timeIntervalSince1970: cityInfo.sunset) - - sunriseTimeLabel.text = "일출 : \(DateFormatter.convertToCityTime(by: sunriseDate))" - sunsetTimeLabel.text = "일몰 : \(DateFormatter.convertToCityTime(by: sunsetDate))" - } + + let sunriseDate = Date(timeIntervalSince1970: cityInfo.sunrise) + let sunsetDate = Date(timeIntervalSince1970: cityInfo.sunset) + + sunriseTimeLabel.text = "일출 : \(DateFormatter.convertToCityTime(by: sunriseDate))" + sunsetTimeLabel.text = "일몰 : \(DateFormatter.convertToCityTime(by: sunsetDate))" + Task { let iconName: String = listInfo.weather.icon From 73d1d7dcfac37bb508599d42c3a804a3ef91eb39 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 20:52:45 +0900 Subject: [PATCH 14/21] =?UTF-8?q?Step1:=20DetailView=EC=9D=98=20Image=20?= =?UTF-8?q?=ED=8C=A8=EC=B9=98=EB=A5=BC=20ImageService=EC=97=90=20=EB=A7=A1?= =?UTF-8?q?=EA=B8=B0=EB=8F=84=EB=A1=9D=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scene/Weather/WeatherViewController.swift | 5 ++-- .../WeatherDetailViewController.swift | 23 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 9a1c93e..1f7324b 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -128,10 +128,11 @@ extension WeatherViewController: UITableViewDelegate { let weatherForecastInfo = viewModel.weatherForecast[indexPath.row] let city = viewModel.city let tempUnit = viewModel.tempUnit - + let imageService = WeatherImageService() let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherForecastInfo: weatherForecastInfo, cityInfo: city, - tempUnit: tempUnit) + tempUnit: tempUnit, + imageService: imageService) navigationController?.show(detailViewController, sender: self) } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift index c1f7c43..06e112e 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift @@ -11,10 +11,17 @@ class WeatherDetailViewController: UIViewController { private let cityInfo: City private let tempUnit: TempUnit - init(weatherForecastInfo: WeatherForecastInfo, cityInfo: City, tempUnit: TempUnit) { + private let imageService: WeatherImageService + + init(weatherForecastInfo: WeatherForecastInfo, + cityInfo: City, + tempUnit: TempUnit, + imageService: WeatherImageService) { + self.weatherForecastInfo = weatherForecastInfo self.cityInfo = cityInfo self.tempUnit = tempUnit + self.imageService = imageService super.init(nibName: nil, bundle: nil) } @@ -111,18 +118,12 @@ class WeatherDetailViewController: UIViewController { sunriseTimeLabel.text = "일출 : \(DateFormatter.convertToCityTime(by: sunriseDate))" sunsetTimeLabel.text = "일몰 : \(DateFormatter.convertToCityTime(by: sunsetDate))" + let iconName: String = listInfo.weather.icon - Task { - let iconName: String = listInfo.weather.icon - let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" - - guard let url: URL = URL(string: urlString), - let (data, _) = try? await URLSession.shared.data(from: url), - let image: UIImage = UIImage(data: data) else { - return + imageService.fetchImage(iconName: iconName) { image in + DispatchQueue.main.async { + iconImageView.image = image } - - iconImageView.image = image } } } From 79b5e6263e46450c279b8525185cba8a5a656198 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 21:23:11 +0900 Subject: [PATCH 15/21] =?UTF-8?q?Step1:=20DetailView=EB=A5=BC=20Controller?= =?UTF-8?q?=EC=99=80=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 4 + .../Scene/Weather/WeatherTableViewCell.swift | 2 +- .../Scene/Weather/WeatherViewController.swift | 5 +- .../WeatherDetail/WeatherDetailView.swift | 83 ++++++++++++++ .../WeatherDetailViewController.swift | 101 +++++------------- 5 files changed, 115 insertions(+), 80 deletions(-) create mode 100644 WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index cd27b5c..e3337c6 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 60EC0E892BA0673F0042C815 /* TempUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E882BA0673F0042C815 /* TempUnit.swift */; }; 60EC0E8B2BA087720042C815 /* WeatherJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */; }; 60EC0E8F2BA1C2520042C815 /* WeatherImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E8E2BA1C2520042C815 /* WeatherImageService.swift */; }; + 60EC0E912BA1CD590042C815 /* WeatherDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E902BA1CD590042C815 /* WeatherDetailView.swift */; }; C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = C741F66F2B58F00500A4DDC0 /* Weather.swift */; }; C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */; }; C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */; }; @@ -30,6 +31,7 @@ 60EC0E882BA0673F0042C815 /* TempUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempUnit.swift; sourceTree = ""; }; 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherJSON.swift; sourceTree = ""; }; 60EC0E8E2BA1C2520042C815 /* WeatherImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherImageService.swift; sourceTree = ""; }; + 60EC0E902BA1CD590042C815 /* WeatherDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherDetailView.swift; sourceTree = ""; }; C741F66F2B58F00500A4DDC0 /* Weather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weather.swift; sourceTree = ""; }; C7743D892B21C38100DF0D09 /* WeatherForecast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WeatherForecast.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -76,6 +78,7 @@ isa = PBXGroup; children = ( C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, + 60EC0E902BA1CD590042C815 /* WeatherDetailView.swift */, ); path = WeatherDetail; sourceTree = ""; @@ -243,6 +246,7 @@ C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, 60EC0E8F2BA1C2520042C815 /* WeatherImageService.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, + 60EC0E912BA1CD590042C815 /* WeatherDetailView.swift in Sources */, 60EC0E8B2BA087720042C815 /* WeatherJSON.swift in Sources */, C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */, ); diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift index dd71eaf..f83b8dc 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift @@ -6,7 +6,7 @@ import UIKit -class WeatherTableViewCell: UITableViewCell { +final class WeatherTableViewCell: UITableViewCell { static let identifier = String(describing: WeatherTableViewCell.self) let weatherIcon: UIImageView = UIImageView() diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 1f7324b..6b60399 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -6,7 +6,7 @@ import UIKit -class WeatherViewController: UIViewController { +final class WeatherViewController: UIViewController { private let tableView: UITableView = .init(frame: .zero, style: .plain) private let refreshControl: UIRefreshControl = UIRefreshControl() @@ -110,8 +110,7 @@ extension WeatherViewController: UITableViewDataSource { cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.symbol)" cell.dateLabel.text = DateFormatter.convertToKorean(by: date) - viewModel.fetchImage(iconName: iconName) { [weak self] image in - guard let self = self else { return } + viewModel.fetchImage(iconName: iconName) { image in DispatchQueue.main.async { cell.weatherIcon.image = image } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift new file mode 100644 index 0000000..87ad936 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift @@ -0,0 +1,83 @@ +// +// WeatherDetailView.swift +// WeatherForecast +// +// Created by 홍승완 on 2024/03/13. +// + +import UIKit + +final class WeatherDetailView: UIView { + let iconImageView: UIImageView = UIImageView() + let weatherGroupLabel: UILabel = UILabel() + let weatherDescriptionLabel: UILabel = UILabel() + let temperatureLabel: UILabel = UILabel() + let feelsLikeLabel: UILabel = UILabel() + let maximumTemperatureLable: UILabel = UILabel() + let minimumTemperatureLable: UILabel = UILabel() + let popLabel: UILabel = UILabel() + let humidityLabel: UILabel = UILabel() + let sunriseTimeLabel: UILabel = UILabel() + let sunsetTimeLabel: UILabel = UILabel() + let spacingView: UIView = UIView() + let mainStackView: UIStackView = UIStackView() + + override init(frame: CGRect) { + super.init(frame: .zero) + + setupViews() + setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + [iconImageView, + weatherGroupLabel, + weatherDescriptionLabel, + temperatureLabel, + feelsLikeLabel, + maximumTemperatureLable, + minimumTemperatureLable, + popLabel, + humidityLabel, + sunriseTimeLabel, + sunsetTimeLabel, + spacingView + ].forEach { view in + mainStackView.addArrangedSubview(view) + } + + mainStackView.arrangedSubviews.forEach { subview in + guard let subview: UILabel = subview as? UILabel else { return } + subview.textColor = .black + subview.backgroundColor = .clear + subview.numberOfLines = 1 + subview.textAlignment = .center + subview.font = .preferredFont(forTextStyle: .body) + } + + mainStackView.axis = .vertical + mainStackView.alignment = .center + mainStackView.spacing = 8 + self.addSubview(mainStackView) + mainStackView.translatesAutoresizingMaskIntoConstraints = false + } + + func setupLayout() { + let safeArea: UILayoutGuide = self.safeAreaLayoutGuide + NSLayoutConstraint.activate([ + mainStackView.topAnchor.constraint(equalTo: safeArea.topAnchor), + mainStackView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor), + mainStackView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, + constant: 16), + mainStackView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, + constant: -16), + iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor), + iconImageView.widthAnchor.constraint(equalTo: safeArea.widthAnchor, + multiplier: 0.3) + ]) + } +} diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift index 06e112e..0b4c716 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift @@ -6,10 +6,11 @@ import UIKit -class WeatherDetailViewController: UIViewController { +final class WeatherDetailViewController: UIViewController { private let weatherForecastInfo: WeatherForecastInfo private let cityInfo: City private let tempUnit: TempUnit + private let contentView = WeatherDetailView() private let imageService: WeatherImageService @@ -31,98 +32,46 @@ class WeatherDetailViewController: UIViewController { } override func viewDidLoad() { super.viewDidLoad() + initialSetUp() } + override func loadView() { + view = contentView + } + private func initialSetUp() { view.backgroundColor = .white + contentView.spacingView.backgroundColor = .clear + contentView.spacingView.setContentHuggingPriority(.defaultLow, for: .vertical) + contentView.weatherGroupLabel.font = .preferredFont(forTextStyle: .largeTitle) + contentView.weatherDescriptionLabel.font = .preferredFont(forTextStyle: .largeTitle) let listInfo = weatherForecastInfo let date: Date = Date(timeIntervalSince1970: listInfo.dt) navigationItem.title = DateFormatter.convertToKorean(by: date) - - let iconImageView: UIImageView = UIImageView() - let weatherGroupLabel: UILabel = UILabel() - let weatherDescriptionLabel: UILabel = UILabel() - let temperatureLabel: UILabel = UILabel() - let feelsLikeLabel: UILabel = UILabel() - let maximumTemperatureLable: UILabel = UILabel() - let minimumTemperatureLable: UILabel = UILabel() - let popLabel: UILabel = UILabel() - let humidityLabel: UILabel = UILabel() - let sunriseTimeLabel: UILabel = UILabel() - let sunsetTimeLabel: UILabel = UILabel() - let spacingView: UIView = UIView() - spacingView.backgroundColor = .clear - spacingView.setContentHuggingPriority(.defaultLow, for: .vertical) - - let mainStackView: UIStackView = .init(arrangedSubviews: [ - iconImageView, - weatherGroupLabel, - weatherDescriptionLabel, - temperatureLabel, - feelsLikeLabel, - maximumTemperatureLable, - minimumTemperatureLable, - popLabel, - humidityLabel, - sunriseTimeLabel, - sunsetTimeLabel, - spacingView - ]) - - mainStackView.arrangedSubviews.forEach { subview in - guard let subview: UILabel = subview as? UILabel else { return } - subview.textColor = .black - subview.backgroundColor = .clear - subview.numberOfLines = 1 - subview.textAlignment = .center - subview.font = .preferredFont(forTextStyle: .body) - } - - weatherGroupLabel.font = .preferredFont(forTextStyle: .largeTitle) - weatherDescriptionLabel.font = .preferredFont(forTextStyle: .largeTitle) - - mainStackView.axis = .vertical - mainStackView.alignment = .center - mainStackView.spacing = 8 - view.addSubview(mainStackView) - mainStackView.translatesAutoresizingMaskIntoConstraints = false - - let safeArea: UILayoutGuide = view.safeAreaLayoutGuide - NSLayoutConstraint.activate([ - mainStackView.topAnchor.constraint(equalTo: safeArea.topAnchor), - mainStackView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor), - mainStackView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, - constant: 16), - mainStackView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, - constant: -16), - iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor), - iconImageView.widthAnchor.constraint(equalTo: safeArea.widthAnchor, - multiplier: 0.3) - ]) - - weatherGroupLabel.text = listInfo.weather.main - weatherDescriptionLabel.text = listInfo.weather.description - temperatureLabel.text = "현재 기온 : \(listInfo.main.temp)\(tempUnit.symbol)" - feelsLikeLabel.text = "체감 기온 : \(listInfo.main.feelsLike)\(tempUnit.symbol)" - maximumTemperatureLable.text = "최고 기온 : \(listInfo.main.tempMax)\(tempUnit.symbol)" - minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(tempUnit.symbol)" - popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" - humidityLabel.text = "습도 : \(listInfo.main.humidity)%" - + + contentView.weatherGroupLabel.text = listInfo.weather.main + contentView.weatherDescriptionLabel.text = listInfo.weather.description + contentView.temperatureLabel.text = "현재 기온 : \(listInfo.main.temp)\(tempUnit.symbol)" + contentView.feelsLikeLabel.text = "체감 기온 : \(listInfo.main.feelsLike)\(tempUnit.symbol)" + contentView.maximumTemperatureLable.text = "최고 기온 : \(listInfo.main.tempMax)\(tempUnit.symbol)" + contentView.minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(tempUnit.symbol)" + contentView.popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" + contentView.humidityLabel.text = "습도 : \(listInfo.main.humidity)%" let sunriseDate = Date(timeIntervalSince1970: cityInfo.sunrise) let sunsetDate = Date(timeIntervalSince1970: cityInfo.sunset) - sunriseTimeLabel.text = "일출 : \(DateFormatter.convertToCityTime(by: sunriseDate))" - sunsetTimeLabel.text = "일몰 : \(DateFormatter.convertToCityTime(by: sunsetDate))" + contentView.sunriseTimeLabel.text = "일출 : \(DateFormatter.convertToCityTime(by: sunriseDate))" + contentView.sunsetTimeLabel.text = "일몰 : \(DateFormatter.convertToCityTime(by: sunsetDate))" let iconName: String = listInfo.weather.icon imageService.fetchImage(iconName: iconName) { image in - DispatchQueue.main.async { - iconImageView.image = image + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + contentView.iconImageView.image = image } } } From 4336d9b04e6c990ab078b972caa11c660e861118 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 21:46:33 +0900 Subject: [PATCH 16/21] =?UTF-8?q?Step1:=20WeatherViewModel=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 4 -- .../Resource/SceneDelegate.swift | 9 +-- .../Scene/Weather/WeatherViewController.swift | 64 +++++++++++-------- .../Scene/Weather/WeatherViewModel.swift | 59 ----------------- 4 files changed, 42 insertions(+), 94 deletions(-) delete mode 100644 WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index e3337c6..64fe31f 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 60EC0E812BA032760042C815 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E802BA032760042C815 /* DateFormatter+.swift */; }; 60EC0E852BA0431A0042C815 /* WeatherJSONService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E842BA0431A0042C815 /* WeatherJSONService.swift */; }; - 60EC0E872BA044340042C815 /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E862BA044340042C815 /* WeatherViewModel.swift */; }; 60EC0E892BA0673F0042C815 /* TempUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E882BA0673F0042C815 /* TempUnit.swift */; }; 60EC0E8B2BA087720042C815 /* WeatherJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */; }; 60EC0E8F2BA1C2520042C815 /* WeatherImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EC0E8E2BA1C2520042C815 /* WeatherImageService.swift */; }; @@ -27,7 +26,6 @@ /* Begin PBXFileReference section */ 60EC0E802BA032760042C815 /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; 60EC0E842BA0431A0042C815 /* WeatherJSONService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherJSONService.swift; sourceTree = ""; }; - 60EC0E862BA044340042C815 /* WeatherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewModel.swift; sourceTree = ""; }; 60EC0E882BA0673F0042C815 /* TempUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempUnit.swift; sourceTree = ""; }; 60EC0E8A2BA087720042C815 /* WeatherJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherJSON.swift; sourceTree = ""; }; 60EC0E8E2BA1C2520042C815 /* WeatherImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherImageService.swift; sourceTree = ""; }; @@ -68,7 +66,6 @@ isa = PBXGroup; children = ( C7743D902B21C38100DF0D09 /* WeatherViewController.swift */, - 60EC0E862BA044340042C815 /* WeatherViewModel.swift */, C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, ); path = Weather; @@ -237,7 +234,6 @@ buildActionMask = 2147483647; files = ( 60EC0E852BA0431A0042C815 /* WeatherJSONService.swift in Sources */, - 60EC0E872BA044340042C815 /* WeatherViewModel.swift in Sources */, C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, 60EC0E892BA0673F0042C815 /* TempUnit.swift in Sources */, C7743D912B21C38100DF0D09 /* WeatherViewController.swift in Sources */, diff --git a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift index dcb396c..4523274 100644 --- a/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift +++ b/WeatherForecast/WeatherForecast/Resource/SceneDelegate.swift @@ -17,10 +17,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let weatherService: WeatherJSONService = WeatherJSONService() let imageService: WeatherImageService = WeatherImageService() - let viewModel = WeatherViewModelImp(weatherService: weatherService, - imageService: imageService, - tempUnit: TempUnit.fahrenheit) - let viewController = WeatherViewController(viewModel: viewModel) + let tempUnit = TempUnit.fahrenheit + + let viewController = WeatherViewController(tempUnit: tempUnit, + weatherService: weatherService, + imageService: imageService) let navigationController = UINavigationController(rootViewController: viewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 6b60399..65db97c 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -10,11 +10,18 @@ final class WeatherViewController: UIViewController { private let tableView: UITableView = .init(frame: .zero, style: .plain) private let refreshControl: UIRefreshControl = UIRefreshControl() - private let viewModel: WeatherViewModel - - init(viewModel: WeatherViewModel) { - self.viewModel = viewModel - + private var weatherJSON: WeatherJSON? + private var tempUnit: TempUnit + private let weatherService: WeatherJSONService + private let imageService: WeatherImageService + + init(tempUnit: TempUnit, + weatherService: WeatherJSONService, + imageService: WeatherImageService) { + self.tempUnit = tempUnit + self.weatherService = weatherService + self.imageService = imageService + super.init(nibName: nil, bundle: nil) } @@ -31,30 +38,33 @@ final class WeatherViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - viewModel.fetch() + weatherService.fetchWeather { [weak self] weatherJSON in + guard let self = self else { return } + + self.weatherJSON = weatherJSON + navigationItem.title = weatherJSON.city.name + } } } extension WeatherViewController { @objc private func changeTempUnit() { - viewModel.changeTempUnit() + + tempUnit.toggle() refresh() } @objc private func refresh() { - viewModel.fetch() tableView.reloadData() refreshControl.endRefreshing() - navigationItem.rightBarButtonItem?.title = viewModel.navigationBarItemTitle - navigationItem.title = viewModel.city.name + navigationItem.title = weatherJSON?.city.name } private func initialSetUp() { view.backgroundColor = .systemBackground - let tempUnit = viewModel.tempUnit navigationItem.rightBarButtonItem = UIBarButtonItem(title: tempUnit.description, image: nil, target: self, action: #selector(changeTempUnit)) - navigationItem.title = viewModel.city.name + navigationItem.title = weatherJSON?.city.name layTable() @@ -90,18 +100,18 @@ extension WeatherViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return viewModel.weatherForecast.count + return weatherJSON?.weatherForecast.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: WeatherTableViewCell.identifier, for: indexPath) - guard let cell: WeatherTableViewCell = cell as? WeatherTableViewCell else { + guard let cell: WeatherTableViewCell = cell as? WeatherTableViewCell, + let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] else { return cell } - - let weatherForecastInfo = viewModel.weatherForecast[indexPath.row] - let tempUnit = viewModel.tempUnit + + let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) let iconName: String = weatherForecastInfo.weather.icon @@ -110,7 +120,7 @@ extension WeatherViewController: UITableViewDataSource { cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.symbol)" cell.dateLabel.text = DateFormatter.convertToKorean(by: date) - viewModel.fetchImage(iconName: iconName) { image in + imageService.fetchImage(iconName: iconName) { image in DispatchQueue.main.async { cell.weatherIcon.image = image } @@ -124,16 +134,16 @@ extension WeatherViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - let weatherForecastInfo = viewModel.weatherForecast[indexPath.row] - let city = viewModel.city - let tempUnit = viewModel.tempUnit + let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] + let city = weatherJSON?.city + let tempUnit = tempUnit let imageService = WeatherImageService() - let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherForecastInfo: weatherForecastInfo, - cityInfo: city, - tempUnit: tempUnit, - imageService: imageService) - - navigationController?.show(detailViewController, sender: self) +// let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherForecastInfo: weatherForecastInfo, +// cityInfo: city, +// tempUnit: tempUnit, +// imageService: imageService) +// +// navigationController?.show(detailViewController, sender: self) } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift deleted file mode 100644 index 1b9f8b6..0000000 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewModel.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// WeatherViewModel.swift -// WeatherForecast -// -// Created by 홍승완 on 2024/03/12. -// - -import Foundation -import UIKit - -protocol WeatherViewModel { - var weatherForecast: [WeatherForecastInfo] { get } - var city: City { get } - var tempUnit: TempUnit { get } - var navigationBarItemTitle: String { get } - - func fetch() - func changeTempUnit() - func fetchImage(iconName: String, completion: @escaping (UIImage) -> ()) -} - -final class WeatherViewModelImp: WeatherViewModel { - private(set) var weatherForecast: [WeatherForecastInfo] = [] - private(set) var city: City = City.mock - private(set) var tempUnit: TempUnit - private let weatherService: WeatherJSONService - private let imageService: WeatherImageService - - var navigationBarItemTitle: String { - return tempUnit.description - } - - init(weatherService: WeatherJSONService, - imageService: WeatherImageService, - tempUnit: TempUnit) { - self.weatherService = weatherService - self.imageService = imageService - self.tempUnit = tempUnit - } - - func fetch() { - weatherService.fetchWeather { [weak self] data in - guard let self = self else { return } - - self.weatherForecast = data.weatherForecast - self.city = data.city - } - } - - func changeTempUnit() { - tempUnit.toggle() - } - - func fetchImage(iconName: String, completion: @escaping (UIImage) -> ()) { - imageService.fetchImage(iconName: iconName) { image in - completion(image) - } - } -} From 00efcbb91899868522405d12489f0fbaf8c5d840 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 21:55:37 +0900 Subject: [PATCH 17/21] =?UTF-8?q?Step1:=20DetailViewController=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scene/Weather/WeatherViewController.swift | 10 +++--- .../WeatherDetailViewController.swift | 32 +++++++++++-------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 65db97c..64d1345 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -138,12 +138,10 @@ extension WeatherViewController: UITableViewDelegate { let city = weatherJSON?.city let tempUnit = tempUnit let imageService = WeatherImageService() -// let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherForecastInfo: weatherForecastInfo, -// cityInfo: city, -// tempUnit: tempUnit, -// imageService: imageService) -// -// navigationController?.show(detailViewController, sender: self) + let weatherDetailInfo = WeatherDetailInfo(weatherForecastInfo: weatherForecastInfo, cityInfo: city, tempUnit: tempUnit) + let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherDetailInfo: weatherDetailInfo, imageService: imageService) + + navigationController?.show(detailViewController, sender: self) } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift index 0b4c716..d8f13e1 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailViewController.swift @@ -6,22 +6,22 @@ import UIKit +struct WeatherDetailInfo { + let weatherForecastInfo: WeatherForecastInfo? + let cityInfo: City? + let tempUnit: TempUnit +} + final class WeatherDetailViewController: UIViewController { - private let weatherForecastInfo: WeatherForecastInfo - private let cityInfo: City - private let tempUnit: TempUnit + private let weatherDetailInfo: WeatherDetailInfo private let contentView = WeatherDetailView() private let imageService: WeatherImageService - init(weatherForecastInfo: WeatherForecastInfo, - cityInfo: City, - tempUnit: TempUnit, + init(weatherDetailInfo: WeatherDetailInfo, imageService: WeatherImageService) { - self.weatherForecastInfo = weatherForecastInfo - self.cityInfo = cityInfo - self.tempUnit = tempUnit + self.weatherDetailInfo = weatherDetailInfo self.imageService = imageService super.init(nibName: nil, bundle: nil) @@ -47,16 +47,20 @@ final class WeatherDetailViewController: UIViewController { contentView.weatherGroupLabel.font = .preferredFont(forTextStyle: .largeTitle) contentView.weatherDescriptionLabel.font = .preferredFont(forTextStyle: .largeTitle) - let listInfo = weatherForecastInfo + guard let listInfo = weatherDetailInfo.weatherForecastInfo, + let cityInfo = weatherDetailInfo.cityInfo else { + return + } + let date: Date = Date(timeIntervalSince1970: listInfo.dt) navigationItem.title = DateFormatter.convertToKorean(by: date) contentView.weatherGroupLabel.text = listInfo.weather.main contentView.weatherDescriptionLabel.text = listInfo.weather.description - contentView.temperatureLabel.text = "현재 기온 : \(listInfo.main.temp)\(tempUnit.symbol)" - contentView.feelsLikeLabel.text = "체감 기온 : \(listInfo.main.feelsLike)\(tempUnit.symbol)" - contentView.maximumTemperatureLable.text = "최고 기온 : \(listInfo.main.tempMax)\(tempUnit.symbol)" - contentView.minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(tempUnit.symbol)" + contentView.temperatureLabel.text = "현재 기온 : \(listInfo.main.temp)\(weatherDetailInfo.tempUnit.symbol)" + contentView.feelsLikeLabel.text = "체감 기온 : \(listInfo.main.feelsLike)\(weatherDetailInfo.tempUnit.symbol)" + contentView.maximumTemperatureLable.text = "최고 기온 : \(listInfo.main.tempMax)\(weatherDetailInfo.tempUnit.symbol)" + contentView.minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(weatherDetailInfo.tempUnit.symbol)" contentView.popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" contentView.humidityLabel.text = "습도 : \(listInfo.main.humidity)%" From d318a58e1cc210853b4bd3d20051e822478ebc16 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 22:04:16 +0900 Subject: [PATCH 18/21] =?UTF-8?q?Step1:=20=EA=B0=9C=ED=96=89=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=B3=80=EC=88=98=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Model/Weather.swift | 20 -------- .../Scene/Weather/WeatherTableViewCell.swift | 49 +++++++++---------- .../Scene/Weather/WeatherViewController.swift | 19 ++++--- .../WeatherDetail/WeatherDetailView.swift | 20 ++++---- .../Source/Service/WeatherImageService.swift | 2 +- 5 files changed, 45 insertions(+), 65 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Model/Weather.swift b/WeatherForecast/WeatherForecast/Source/Model/Weather.swift index 3ae3ca4..7daaee2 100644 --- a/WeatherForecast/WeatherForecast/Source/Model/Weather.swift +++ b/WeatherForecast/WeatherForecast/Source/Model/Weather.swift @@ -36,29 +36,9 @@ struct City: Decodable { let country: String let population, timezone: Int let sunrise, sunset: TimeInterval - - init(id: Int, name: String, coord: Coord, country: String, population: Int, timezone: Int, sunrise: TimeInterval, sunset: TimeInterval) { - self.id = id - self.name = name - self.coord = coord - self.country = country - self.population = population - self.timezone = timezone - self.sunrise = sunrise - self.sunset = sunset - } - - static let mock: City = City(id: 0, name: "", coord: Coord.mock, country: "", population: 0, timezone: 0, sunrise: 0, sunset: 0) } // MARK: - Coord struct Coord: Decodable { let lat, lon: Double - - init(lat: Double, lon: Double) { - self.lat = lat - self.lon = lon - } - - static let mock: Coord = Coord(lat: 0, lon: 0) } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift index f83b8dc..f829557 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift @@ -16,8 +16,10 @@ final class WeatherTableViewCell: UITableViewCell { let descriptionLabel: UILabel = UILabel() let dashLabel: UILabel = UILabel() - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) + override init(style: UITableViewCell.CellStyle, + reuseIdentifier: String?) { + super.init(style: style, + reuseIdentifier: reuseIdentifier) layViews() reset() } @@ -32,7 +34,11 @@ final class WeatherTableViewCell: UITableViewCell { } private func layViews() { - let labels: [UILabel] = [dateLabel, temperatureLabel, weatherLabel, dashLabel, descriptionLabel] + let labels: [UILabel] = [dateLabel, + temperatureLabel, + weatherLabel, + dashLabel, + descriptionLabel] labels.forEach { label in label.textColor = .black @@ -40,11 +46,9 @@ final class WeatherTableViewCell: UITableViewCell { label.numberOfLines = 1 } - let weatherStackView: UIStackView = UIStackView(arrangedSubviews: [ - weatherLabel, - dashLabel, - descriptionLabel - ]) + let weatherStackView: UIStackView = UIStackView(arrangedSubviews: [weatherLabel, + dashLabel, + descriptionLabel]) descriptionLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) @@ -54,22 +58,17 @@ final class WeatherTableViewCell: UITableViewCell { weatherStackView.alignment = .center weatherStackView.distribution = .fill - - let verticalStackView: UIStackView = UIStackView(arrangedSubviews: [ - dateLabel, - temperatureLabel, - weatherStackView - ]) + let verticalStackView: UIStackView = UIStackView(arrangedSubviews: [dateLabel, + temperatureLabel, + weatherStackView]) verticalStackView.axis = .vertical verticalStackView.spacing = 8 verticalStackView.distribution = .fill verticalStackView.alignment = .leading - let contentsStackView: UIStackView = UIStackView(arrangedSubviews: [ - weatherIcon, - verticalStackView - ]) + let contentsStackView: UIStackView = UIStackView(arrangedSubviews: [weatherIcon, + verticalStackView]) contentsStackView.axis = .horizontal contentsStackView.spacing = 16 @@ -79,14 +78,12 @@ final class WeatherTableViewCell: UITableViewCell { contentView.addSubview(contentsStackView) - NSLayoutConstraint.activate([ - contentsStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), - contentsStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - contentsStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - contentsStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16), - weatherIcon.widthAnchor.constraint(equalTo: weatherIcon.heightAnchor), - weatherIcon.widthAnchor.constraint(equalToConstant: 100) - ]) + NSLayoutConstraint.activate([contentsStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), + contentsStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + contentsStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), + contentsStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16), + weatherIcon.widthAnchor.constraint(equalTo: weatherIcon.heightAnchor), + weatherIcon.widthAnchor.constraint(equalToConstant: 100)]) } private func reset() { diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 64d1345..696ea13 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -12,6 +12,7 @@ final class WeatherViewController: UIViewController { private var weatherJSON: WeatherJSON? private var tempUnit: TempUnit + private let weatherService: WeatherJSONService private let imageService: WeatherImageService @@ -49,7 +50,6 @@ final class WeatherViewController: UIViewController { extension WeatherViewController { @objc private func changeTempUnit() { - tempUnit.toggle() refresh() } @@ -63,7 +63,10 @@ extension WeatherViewController { private func initialSetUp() { view.backgroundColor = .systemBackground - navigationItem.rightBarButtonItem = UIBarButtonItem(title: tempUnit.description, image: nil, target: self, action: #selector(changeTempUnit)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: tempUnit.description, + image: nil, + target: self, + action: #selector(changeTempUnit)) navigationItem.title = weatherJSON?.city.name layTable() @@ -73,7 +76,8 @@ extension WeatherViewController { for: .valueChanged) tableView.refreshControl = refreshControl - tableView.register(WeatherTableViewCell.self, forCellReuseIdentifier: WeatherTableViewCell.identifier) + tableView.register(WeatherTableViewCell.self, + forCellReuseIdentifier: WeatherTableViewCell.identifier) tableView.dataSource = self tableView.delegate = self } @@ -111,7 +115,6 @@ extension WeatherViewController: UITableViewDataSource { return cell } - let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) let iconName: String = weatherForecastInfo.weather.icon @@ -136,10 +139,12 @@ extension WeatherViewController: UITableViewDelegate { let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] let city = weatherJSON?.city - let tempUnit = tempUnit let imageService = WeatherImageService() - let weatherDetailInfo = WeatherDetailInfo(weatherForecastInfo: weatherForecastInfo, cityInfo: city, tempUnit: tempUnit) - let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherDetailInfo: weatherDetailInfo, imageService: imageService) + let weatherDetailInfo = WeatherDetailInfo(weatherForecastInfo: weatherForecastInfo, + cityInfo: city, + tempUnit: tempUnit) + let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherDetailInfo: weatherDetailInfo, + imageService: imageService) navigationController?.show(detailViewController, sender: self) } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift index 87ad936..9f28043 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/WeatherDetail/WeatherDetailView.swift @@ -68,16 +68,14 @@ final class WeatherDetailView: UIView { func setupLayout() { let safeArea: UILayoutGuide = self.safeAreaLayoutGuide - NSLayoutConstraint.activate([ - mainStackView.topAnchor.constraint(equalTo: safeArea.topAnchor), - mainStackView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor), - mainStackView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, - constant: 16), - mainStackView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, - constant: -16), - iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor), - iconImageView.widthAnchor.constraint(equalTo: safeArea.widthAnchor, - multiplier: 0.3) - ]) + NSLayoutConstraint.activate([mainStackView.topAnchor.constraint(equalTo: safeArea.topAnchor), + mainStackView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor), + mainStackView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, + constant: 16), + mainStackView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, + constant: -16), + iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor), + iconImageView.widthAnchor.constraint(equalTo: safeArea.widthAnchor, + multiplier: 0.3)]) } } diff --git a/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift b/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift index 6c36dac..a3565f1 100644 --- a/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift +++ b/WeatherForecast/WeatherForecast/Source/Service/WeatherImageService.swift @@ -9,7 +9,7 @@ import UIKit final class WeatherImageService { - let imageCache: NSCache = NSCache() + private let imageCache: NSCache = NSCache() func fetchImage(iconName: String, completion: @escaping (UIImage) -> ()) { let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" From 3bd5a1030181aa79eea31c00e41e0f802169ea73 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 22:16:20 +0900 Subject: [PATCH 19/21] =?UTF-8?q?Step1:=20WeatherDetail=EC=97=90=20Weather?= =?UTF-8?q?=EC=9D=98=20imageService=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Scene/Weather/WeatherViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 696ea13..734a2c8 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -139,7 +139,6 @@ extension WeatherViewController: UITableViewDelegate { let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] let city = weatherJSON?.city - let imageService = WeatherImageService() let weatherDetailInfo = WeatherDetailInfo(weatherForecastInfo: weatherForecastInfo, cityInfo: city, tempUnit: tempUnit) From d811f9cb036d4b492a80028f821b2f8b6f704a4c Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 22:24:42 +0900 Subject: [PATCH 20/21] =?UTF-8?q?Step1:=20tableView=20configure=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scene/Weather/WeatherTableViewCell.swift | 17 +++++++++++++++++ .../Scene/Weather/WeatherViewController.swift | 16 +++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift index f829557..7ecdc90 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherTableViewCell.swift @@ -93,4 +93,21 @@ final class WeatherTableViewCell: UITableViewCell { weatherLabel.text = "~~~" descriptionLabel.text = "~~~~~" } + + func configure(weatherForecastInfo: WeatherForecastInfo, tempUnit: TempUnit, imageService: WeatherImageService) { + let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) + let iconName: String = weatherForecastInfo.weather.icon + + weatherLabel.text = weatherForecastInfo.weather.main + descriptionLabel.text = weatherForecastInfo.weather.description + temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.symbol)" + dateLabel.text = DateFormatter.convertToKorean(by: date) + + imageService.fetchImage(iconName: iconName) { image in + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + weatherIcon.image = image + } + } + } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 734a2c8..0fc853c 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -115,19 +115,9 @@ extension WeatherViewController: UITableViewDataSource { return cell } - let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) - let iconName: String = weatherForecastInfo.weather.icon - - cell.weatherLabel.text = weatherForecastInfo.weather.main - cell.descriptionLabel.text = weatherForecastInfo.weather.description - cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.symbol)" - cell.dateLabel.text = DateFormatter.convertToKorean(by: date) - - imageService.fetchImage(iconName: iconName) { image in - DispatchQueue.main.async { - cell.weatherIcon.image = image - } - } + cell.configure(weatherForecastInfo: weatherForecastInfo, + tempUnit: tempUnit, + imageService: imageService) return cell } From e6e69e0e137603873c893b34e1b517e66a964575 Mon Sep 17 00:00:00 2001 From: hsw1920 Date: Wed, 13 Mar 2024 22:42:37 +0900 Subject: [PATCH 21/21] =?UTF-8?q?Step1:=20WeatherJSON=20=EC=BA=A1=EC=8A=90?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Model/WeatherJSON.swift | 19 ++++++++++++++++--- .../Scene/Weather/WeatherViewController.swift | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift b/WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift index d8e88e4..872e446 100644 --- a/WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift +++ b/WeatherForecast/WeatherForecast/Source/Model/WeatherJSON.swift @@ -6,7 +6,20 @@ // // MARK: - Weather JSON Format -struct WeatherJSON: Decodable { - let weatherForecast: [WeatherForecastInfo] - let city: City +protocol WeatherDataProtocol { + var weatherForecast: [WeatherForecastInfo] { get } + var city: City { get } +} + +struct WeatherJSON: Decodable, WeatherDataProtocol { + private let _weatherForecast: [WeatherForecastInfo] + private let _city: City + + var weatherForecast: [WeatherForecastInfo] { + return _weatherForecast + } + + var city: City { + return _city + } } diff --git a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift index 0fc853c..2157241 100644 --- a/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift +++ b/WeatherForecast/WeatherForecast/Source/Scene/Weather/WeatherViewController.swift @@ -10,7 +10,7 @@ final class WeatherViewController: UIViewController { private let tableView: UITableView = .init(frame: .zero, style: .plain) private let refreshControl: UIRefreshControl = UIRefreshControl() - private var weatherJSON: WeatherJSON? + private var weatherJSON: WeatherDataProtocol? private var tempUnit: TempUnit private let weatherService: WeatherJSONService