From e550ed13007a5d8e07a86d40c64ede637d58e115 Mon Sep 17 00:00:00 2001 From: iOS-jeongwon Date: Sun, 4 Feb 2024 00:12:14 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix=20:=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=EC=97=AD=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast/ViewController.swift | 45 ++++--------- WeatherForecast/WeatherForecast/Weather.swift | 1 - .../WeatherDetailViewController.swift | 66 +++++++++---------- .../WeatherTableViewCell.swift | 40 ++++++++++- 4 files changed, 83 insertions(+), 69 deletions(-) diff --git a/WeatherForecast/WeatherForecast/ViewController.swift b/WeatherForecast/WeatherForecast/ViewController.swift index 50b66fb..3d62d1e 100644 --- a/WeatherForecast/WeatherForecast/ViewController.swift +++ b/WeatherForecast/WeatherForecast/ViewController.swift @@ -12,6 +12,7 @@ class ViewController: UIViewController { var weatherJSON: WeatherJSON? var icons: [UIImage]? let imageChache: NSCache = NSCache() + weak var delegate: WeatherForecastInfoDelegate? let dateFormatter: DateFormatter = { let formatter: DateFormatter = DateFormatter() formatter.locale = .init(identifier: "ko_KR") @@ -117,36 +118,7 @@ extension ViewController: UITableViewDataSource { let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] else { return cell } - - cell.weatherLabel.text = weatherForecastInfo.weather.main - cell.descriptionLabel.text = weatherForecastInfo.weather.description - cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.expression)" - - let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt) - cell.dateLabel.text = dateFormatter.string(from: date) - - 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) { - 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 - } - - imageChache.setObject(image, forKey: urlString as NSString) - - if indexPath == tableView.indexPath(for: cell) { - cell.weatherIcon.image = image - } - } - + cell.configure(info: weatherForecastInfo, tempUnit: tempUnit) return cell } } @@ -156,11 +128,16 @@ extension ViewController: UITableViewDelegate { tableView.deselectRow(at: indexPath, animated: true) let detailViewController: WeatherDetailViewController = WeatherDetailViewController() - detailViewController.weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] - detailViewController.cityInfo = weatherJSON?.city - detailViewController.tempUnit = tempUnit + self.delegate = detailViewController + guard let weatherInfo = weatherJSON?.weatherForecast[indexPath.row] else { return } + guard let cityInfo = weatherJSON?.city else { return } + delegate?.updateWeatherInfo(listInfo: weatherInfo,tempUnit: tempUnit) + delegate?.updateCityInfo(cityInfo) navigationController?.show(detailViewController, sender: self) } } - +protocol WeatherForecastInfoDelegate: AnyObject{ + func updateWeatherInfo(listInfo: WeatherForecastInfo, tempUnit: TempUnit) + func updateCityInfo(_ cityInfo: City) +} diff --git a/WeatherForecast/WeatherForecast/Weather.swift b/WeatherForecast/WeatherForecast/Weather.swift index ede7585..6d35919 100644 --- a/WeatherForecast/WeatherForecast/Weather.swift +++ b/WeatherForecast/WeatherForecast/Weather.swift @@ -59,4 +59,3 @@ enum TempUnit: String { } } } - diff --git a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift index 69d3dfb..2163690 100644 --- a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift @@ -7,10 +7,19 @@ import UIKit class WeatherDetailViewController: UIViewController { - - var weatherForecastInfo: WeatherForecastInfo? - var cityInfo: City? - var tempUnit: TempUnit = .metric + + 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 dateFormatter: DateFormatter = { let formatter: DateFormatter = DateFormatter() @@ -26,24 +35,6 @@ class WeatherDetailViewController: UIViewController { private func initialSetUp() { view.backgroundColor = .white - - guard let listInfo = weatherForecastInfo else { return } - - let date: Date = Date(timeIntervalSince1970: listInfo.dt) - navigationItem.title = dateFormatter.string(from: 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) @@ -92,7 +83,15 @@ class WeatherDetailViewController: UIViewController { iconImageView.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.3) ]) - + } + +} + +extension WeatherDetailViewController: WeatherForecastInfoDelegate { + + func updateWeatherInfo(listInfo: WeatherForecastInfo, tempUnit: TempUnit){ + let date: Date = Date(timeIntervalSince1970: listInfo.dt) + navigationItem.title = dateFormatter.string(from: date) weatherGroupLabel.text = listInfo.weather.main weatherDescriptionLabel.text = listInfo.weather.description temperatureLabel.text = "현재 기온 : \(listInfo.main.temp)\(tempUnit.expression)" @@ -101,16 +100,7 @@ class WeatherDetailViewController: UIViewController { minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(tempUnit.expression)" popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" 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)))" - } - + Task { let iconName: String = listInfo.weather.icon let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" @@ -124,4 +114,14 @@ class WeatherDetailViewController: UIViewController { iconImageView.image = image } } + + func updateCityInfo(_ cityInfo: City) { + 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)))" + } + } diff --git a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift index 42cb519..84c4a90 100644 --- a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift @@ -12,7 +12,15 @@ class WeatherTableViewCell: UITableViewCell { var temperatureLabel: UILabel! var weatherLabel: UILabel! var descriptionLabel: UILabel! - + var 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 + }() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) layViews() @@ -100,4 +108,34 @@ class WeatherTableViewCell: UITableViewCell { weatherLabel.text = "~~~" descriptionLabel.text = "~~~~~" } + func configure(info: WeatherForecastInfo,tempUnit : TempUnit){ + weatherLabel.text = info.weather.main + descriptionLabel.text = info.weather.description + temperatureLabel.text = "\(info.main.temp)\(tempUnit.expression)" + let date: Date = Date(timeIntervalSince1970: info.dt) + dateLabel.text = dateFormatter.string(from: date) + loadImage(info) + } +} +extension WeatherTableViewCell { + func loadImage(_ info : WeatherForecastInfo){ + + let iconName: String = info.weather.icon + let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" + + if let image = imageChache.object(forKey: urlString as NSString) { + weatherIcon.image = image + return + } + + 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 + } + weatherIcon.image = image + imageChache.setObject(image, forKey: urlString as NSString) + } + } } From 0f150ea0cbd86f672000192ef15aa05c0b1a2a57 Mon Sep 17 00:00:00 2001 From: gadisom Date: Sun, 4 Feb 2024 15:46:12 +0900 Subject: [PATCH 2/7] =?UTF-8?q?Add=20:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast/ViewController.swift | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/WeatherForecast/WeatherForecast/ViewController.swift b/WeatherForecast/WeatherForecast/ViewController.swift index 3d62d1e..1eb0262 100644 --- a/WeatherForecast/WeatherForecast/ViewController.swift +++ b/WeatherForecast/WeatherForecast/ViewController.swift @@ -11,15 +11,7 @@ class ViewController: UIViewController { let refreshControl: UIRefreshControl = UIRefreshControl() var weatherJSON: WeatherJSON? var icons: [UIImage]? - let imageChache: NSCache = NSCache() weak var delegate: WeatherForecastInfoDelegate? - 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() { @@ -42,7 +34,7 @@ extension ViewController { } @objc private func refresh() { - fetchWeatherJSON() + loadJSON() tableView.reloadData() refreshControl.endRefreshing() } @@ -79,24 +71,8 @@ extension ViewController { } extension ViewController { - 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 + private func loadJSON() { + weatherJSON = NetworkService.shared.fetchWeatherJSON(weatherInfo: weatherJSON) navigationItem.title = weatherJSON?.city.name } } @@ -141,3 +117,24 @@ protocol WeatherForecastInfoDelegate: AnyObject{ func updateWeatherInfo(listInfo: WeatherForecastInfo, tempUnit: TempUnit) func updateCityInfo(_ cityInfo: City) } + +class NetworkService { + static let shared = NetworkService() + private init() {} + + func fetchWeatherJSON(weatherInfo :WeatherJSON?) -> WeatherJSON?{ + let jsonDecoder = JSONDecoder() + jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase + guard let data = NSDataAsset(name: "weather")?.data else { + return nil + } + let info: WeatherJSON + do { + info = try jsonDecoder.decode(WeatherJSON.self, from: data) + } catch { + print(error.localizedDescription) + return nil + } + return info + } +} From e376d43d658f1ecf424a4abe8fa17c127657c1a8 Mon Sep 17 00:00:00 2001 From: gadisom Date: Sun, 4 Feb 2024 20:55:02 +0900 Subject: [PATCH 3/7] [STEP1] --- .../WeatherForecast.xcodeproj/project.pbxproj | 8 ++++ .../WeatherForecast/NetworkService.swift | 40 ++++++++++++++++ .../WeatherForecast/Protocol.swift | 14 ++++++ .../WeatherForecast/ViewController.swift | 47 ++++++------------- .../WeatherDetailViewController.swift | 35 ++++++++------ .../WeatherTableViewCell.swift | 24 +++------- 6 files changed, 103 insertions(+), 65 deletions(-) create mode 100644 WeatherForecast/WeatherForecast/NetworkService.swift create mode 100644 WeatherForecast/WeatherForecast/Protocol.swift diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index 9b2f170..6e87f22 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ C7743D992B21C38200DF0D09 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */; }; C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */; }; C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */; }; + FFC9B6D92B6FB0DD00BBFBE1 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC9B6D82B6FB0DD00BBFBE1 /* NetworkService.swift */; }; + FFC9B6DB2B6FB11F00BBFBE1 /* Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -30,6 +32,8 @@ C7743D9A2B21C38200DF0D09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTableViewCell.swift; sourceTree = ""; }; C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherDetailViewController.swift; sourceTree = ""; }; + FFC9B6D82B6FB0DD00BBFBE1 /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; + FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Protocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,7 +69,9 @@ C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */, C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */, C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, + FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */, C7743D902B21C38100DF0D09 /* ViewController.swift */, + FFC9B6D82B6FB0DD00BBFBE1 /* NetworkService.swift */, C741F66F2B58F00500A4DDC0 /* Weather.swift */, C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, C7743D922B21C38100DF0D09 /* Main.storyboard */, @@ -150,9 +156,11 @@ C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, C7743D912B21C38100DF0D09 /* ViewController.swift in Sources */, C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, + FFC9B6DB2B6FB11F00BBFBE1 /* Protocol.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */, + FFC9B6D92B6FB0DD00BBFBE1 /* NetworkService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WeatherForecast/WeatherForecast/NetworkService.swift b/WeatherForecast/WeatherForecast/NetworkService.swift new file mode 100644 index 0000000..9e6644e --- /dev/null +++ b/WeatherForecast/WeatherForecast/NetworkService.swift @@ -0,0 +1,40 @@ +// +// NetworkService.swift +// WeatherForecast +// +// Created by 김정원 on 2/4/24. +// +import Foundation +import UIKit +final class NetworkService { + static let shared = NetworkService() + private init() {} + + func fetchWeatherJSON(weatherInfo :WeatherJSON?) -> WeatherJSON?{ + let jsonDecoder = JSONDecoder() + jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase + guard let data = NSDataAsset(name: "weather")?.data else { + return nil + } + let info: WeatherJSON + do { + info = try jsonDecoder.decode(WeatherJSON.self, from: data) + } catch { + print(error.localizedDescription) + return nil + } + return info + } + func fetchWeatherIconImage(iconName: String) async -> UIImage? { + let urlString = "https://openweathermap.org/img/wn/\(iconName)@2x.png" + guard let url = URL(string: urlString) else { return nil } + + do { + let (data, _) = try await URLSession.shared.data(from: url) + return UIImage(data: data) + } catch { + print("Error fetching weather icon: \(error)") + return nil + } + } +} diff --git a/WeatherForecast/WeatherForecast/Protocol.swift b/WeatherForecast/WeatherForecast/Protocol.swift new file mode 100644 index 0000000..9c6cbc1 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Protocol.swift @@ -0,0 +1,14 @@ +// +// Protocol.swift +// WeatherForecast +// +// Created by 김정원 on 2/4/24. +// + +import Foundation + +protocol WeatherForeCastDelegate: AnyObject { + func fetchWeatherInfo() -> WeatherForecastInfo + func fetchCityInfo() -> City + func fetchTempUnit() -> TempUnit +} diff --git a/WeatherForecast/WeatherForecast/ViewController.swift b/WeatherForecast/WeatherForecast/ViewController.swift index 1eb0262..d6674cc 100644 --- a/WeatherForecast/WeatherForecast/ViewController.swift +++ b/WeatherForecast/WeatherForecast/ViewController.swift @@ -6,14 +6,13 @@ import UIKit -class ViewController: UIViewController { +final class ViewController: UIViewController { var tableView: UITableView! let refreshControl: UIRefreshControl = UIRefreshControl() var weatherJSON: WeatherJSON? var icons: [UIImage]? - weak var delegate: WeatherForecastInfoDelegate? var tempUnit: TempUnit = .metric - + var selectIndex : IndexPath! override func viewDidLoad() { super.viewDidLoad() initialSetUp() @@ -89,7 +88,6 @@ extension ViewController: UITableViewDataSource { 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 { return cell @@ -102,39 +100,22 @@ extension ViewController: UITableViewDataSource { extension ViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - + selectIndex = indexPath let detailViewController: WeatherDetailViewController = WeatherDetailViewController() - self.delegate = detailViewController - guard let weatherInfo = weatherJSON?.weatherForecast[indexPath.row] else { return } - guard let cityInfo = weatherJSON?.city else { return } - delegate?.updateWeatherInfo(listInfo: weatherInfo,tempUnit: tempUnit) - delegate?.updateCityInfo(cityInfo) + detailViewController.delegate = self navigationController?.show(detailViewController, sender: self) } } -protocol WeatherForecastInfoDelegate: AnyObject{ - func updateWeatherInfo(listInfo: WeatherForecastInfo, tempUnit: TempUnit) - func updateCityInfo(_ cityInfo: City) -} - -class NetworkService { - static let shared = NetworkService() - private init() {} - - func fetchWeatherJSON(weatherInfo :WeatherJSON?) -> WeatherJSON?{ - let jsonDecoder = JSONDecoder() - jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase - guard let data = NSDataAsset(name: "weather")?.data else { - return nil - } - let info: WeatherJSON - do { - info = try jsonDecoder.decode(WeatherJSON.self, from: data) - } catch { - print(error.localizedDescription) - return nil - } - return info +extension ViewController: WeatherForeCastDelegate{ + + func fetchWeatherInfo() -> WeatherForecastInfo { + return (NetworkService.shared.fetchWeatherJSON(weatherInfo: weatherJSON)?.weatherForecast[selectIndex.row])! // 선택한 셀의 정보 넘기기 + } + func fetchCityInfo() -> City { + return (NetworkService.shared.fetchWeatherJSON(weatherInfo: weatherJSON)?.city)! + } + func fetchTempUnit() -> TempUnit { + return tempUnit } } diff --git a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift index 2163690..dc5cf87 100644 --- a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift @@ -6,7 +6,7 @@ import UIKit -class WeatherDetailViewController: UIViewController { +final class WeatherDetailViewController: UIViewController { let iconImageView: UIImageView = UIImageView() let weatherGroupLabel: UILabel = UILabel() @@ -20,7 +20,8 @@ class WeatherDetailViewController: UIViewController { let sunriseTimeLabel: UILabel = UILabel() let sunsetTimeLabel: UILabel = UILabel() let spacingView: UIView = UIView() - + weak var delegate: WeatherForeCastDelegate? + var weahterJSON: WeatherJSON! let dateFormatter: DateFormatter = { let formatter: DateFormatter = DateFormatter() formatter.locale = .init(identifier: "ko_KR") @@ -31,6 +32,7 @@ class WeatherDetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() initialSetUp() + setupData() } private func initialSetUp() { @@ -84,10 +86,18 @@ class WeatherDetailViewController: UIViewController { multiplier: 0.3) ]) } - + func setupData(){ + guard let weatherInfo = delegate?.fetchWeatherInfo(), + let cityInfo = delegate?.fetchCityInfo(), + let tempUnit = delegate?.fetchTempUnit() + else { return } + updateWeatherInfo(listInfo: weatherInfo, tempUnit: tempUnit) + updateCityInfo(cityInfo) + updateWeatherIcon(iconName: weatherInfo.weather.icon) + } } -extension WeatherDetailViewController: WeatherForecastInfoDelegate { +extension WeatherDetailViewController { func updateWeatherInfo(listInfo: WeatherForecastInfo, tempUnit: TempUnit){ let date: Date = Date(timeIntervalSince1970: listInfo.dt) @@ -100,21 +110,16 @@ extension WeatherDetailViewController: WeatherForecastInfoDelegate { minimumTemperatureLable.text = "최저 기온 : \(listInfo.main.tempMin)\(tempUnit.expression)" popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" humidityLabel.text = "습도 : \(listInfo.main.humidity)%" - + } + func updateWeatherIcon(iconName: String){ 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 + if let iconImage = await NetworkService.shared.fetchWeatherIconImage(iconName: iconName) { + DispatchQueue.main.async { + self.iconImageView.image = iconImage + } } - - iconImageView.image = image } } - func updateCityInfo(_ cityInfo: City) { let formatter: DateFormatter = DateFormatter() formatter.dateFormat = .none diff --git a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift index 84c4a90..645c40e 100644 --- a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift @@ -114,28 +114,18 @@ class WeatherTableViewCell: UITableViewCell { temperatureLabel.text = "\(info.main.temp)\(tempUnit.expression)" let date: Date = Date(timeIntervalSince1970: info.dt) dateLabel.text = dateFormatter.string(from: date) - loadImage(info) + loadImage(info.weather.icon) } } extension WeatherTableViewCell { - func loadImage(_ info : WeatherForecastInfo){ - - let iconName: String = info.weather.icon - let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" - - if let image = imageChache.object(forKey: urlString as NSString) { - weatherIcon.image = image - return - } - + + func loadImage(_ iconName: String){ 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 + if let iconImage = await NetworkService.shared.fetchWeatherIconImage(iconName: iconName) { + DispatchQueue.main.async { + self.weatherIcon.image = iconImage + } } - weatherIcon.image = image - imageChache.setObject(image, forKey: urlString as NSString) } } } From e484086e752158097051a5cc0223e72fdeeb5a8d Mon Sep 17 00:00:00 2001 From: gadisom Date: Tue, 6 Feb 2024 23:12:52 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Refactor:=20=EB=A6=AC=EB=B7=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 16 +-- .../Base.lproj/Main.storyboard | 4 +- ...etworkService.swift => TransforJSON.swift} | 7 +- .../WeatherDetailViewController.swift | 99 ++++++++++--------- ...ft => WeatherForecastViewController.swift} | 93 +++++++++++++---- .../WeatherTableViewCell.swift | 15 ++- 6 files changed, 143 insertions(+), 91 deletions(-) rename WeatherForecast/WeatherForecast/{NetworkService.swift => TransforJSON.swift} (87%) rename WeatherForecast/WeatherForecast/{ViewController.swift => WeatherForecastViewController.swift} (51%) diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index 6e87f22..fe4e46a 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -10,13 +10,13 @@ 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 */; }; + C7743D912B21C38100DF0D09 /* WeatherForecastViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D902B21C38100DF0D09 /* WeatherForecastViewController.swift */; }; C7743D942B21C38100DF0D09 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7743D922B21C38100DF0D09 /* Main.storyboard */; }; 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 */; }; C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */; }; - FFC9B6D92B6FB0DD00BBFBE1 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC9B6D82B6FB0DD00BBFBE1 /* NetworkService.swift */; }; + FFC9B6D92B6FB0DD00BBFBE1 /* TransforJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */; }; FFC9B6DB2B6FB11F00BBFBE1 /* Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */; }; /* End PBXBuildFile section */ @@ -25,14 +25,14 @@ 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 = ""; }; + C7743D902B21C38100DF0D09 /* WeatherForecastViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherForecastViewController.swift; sourceTree = ""; }; C7743D932B21C38100DF0D09 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; 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 = ""; }; C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTableViewCell.swift; sourceTree = ""; }; C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherDetailViewController.swift; sourceTree = ""; }; - FFC9B6D82B6FB0DD00BBFBE1 /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; + FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransforJSON.swift; sourceTree = ""; }; FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Protocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -70,8 +70,8 @@ C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */, C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */, - C7743D902B21C38100DF0D09 /* ViewController.swift */, - FFC9B6D82B6FB0DD00BBFBE1 /* NetworkService.swift */, + FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */, + C7743D902B21C38100DF0D09 /* WeatherForecastViewController.swift */, C741F66F2B58F00500A4DDC0 /* Weather.swift */, C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, C7743D922B21C38100DF0D09 /* Main.storyboard */, @@ -154,13 +154,13 @@ buildActionMask = 2147483647; files = ( C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, - C7743D912B21C38100DF0D09 /* ViewController.swift in Sources */, + C7743D912B21C38100DF0D09 /* WeatherForecastViewController.swift in Sources */, C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, FFC9B6DB2B6FB11F00BBFBE1 /* Protocol.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */, - FFC9B6D92B6FB0DD00BBFBE1 /* NetworkService.swift in Sources */, + FFC9B6D92B6FB0DD00BBFBE1 /* TransforJSON.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard b/WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard index 4798dc7..14cb404 100644 --- a/WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard +++ b/WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard @@ -8,10 +8,10 @@ - + - + diff --git a/WeatherForecast/WeatherForecast/NetworkService.swift b/WeatherForecast/WeatherForecast/TransforJSON.swift similarity index 87% rename from WeatherForecast/WeatherForecast/NetworkService.swift rename to WeatherForecast/WeatherForecast/TransforJSON.swift index 9e6644e..1c70a0f 100644 --- a/WeatherForecast/WeatherForecast/NetworkService.swift +++ b/WeatherForecast/WeatherForecast/TransforJSON.swift @@ -4,13 +4,12 @@ // // Created by 김정원 on 2/4/24. // -import Foundation import UIKit -final class NetworkService { - static let shared = NetworkService() +final class TransforJSON { + static let shared = TransforJSON() private init() {} - func fetchWeatherJSON(weatherInfo :WeatherJSON?) -> WeatherJSON?{ + func fetchWeatherJSON(weatherInfo: WeatherJSON?) -> WeatherJSON?{ let jsonDecoder = JSONDecoder() jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase guard let data = NSDataAsset(name: "weather")?.data else { diff --git a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift index dc5cf87..26fb508 100644 --- a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift @@ -8,31 +8,47 @@ import UIKit final class WeatherDetailViewController: UIViewController { - 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() - weak var delegate: WeatherForeCastDelegate? - var weahterJSON: WeatherJSON! + let iconImageView: UIImageView = .init() + let weatherGroupLabel: UILabel = .init() + let weatherDescriptionLabel: UILabel = .init() + let temperatureLabel: UILabel = .init() + let feelsLikeLabel: UILabel = .init() + let maximumTemperatureLable: UILabel = .init() + let minimumTemperatureLable: UILabel = .init() + let popLabel: UILabel = .init() + let humidityLabel: UILabel = .init() + let sunriseTimeLabel: UILabel = .init() + let sunsetTimeLabel: UILabel = .init() + let spacingView: UIView = .init() + var weatherInfo: WeatherDetailInfo + var cityInfo: CityDetailInfo + var tempUnit: TempUnit let dateFormatter: DateFormatter = { let formatter: DateFormatter = DateFormatter() formatter.locale = .init(identifier: "ko_KR") formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm" return formatter }() + + init(weatherInfo: WeatherDetailInfo, cityInfo: CityDetailInfo, tempUnit: TempUnit) { + self.weatherInfo = weatherInfo + 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() - setupData() + updateWeatherInfo(listInfo: weatherInfo, tempUnit: tempUnit) + updateCityInfo(cityInfo) + Task { + await updateWeatherIcon(iconName: weatherInfo.iconImageUrl) + } } private func initialSetUp() { @@ -86,47 +102,32 @@ final class WeatherDetailViewController: UIViewController { multiplier: 0.3) ]) } - func setupData(){ - guard let weatherInfo = delegate?.fetchWeatherInfo(), - let cityInfo = delegate?.fetchCityInfo(), - let tempUnit = delegate?.fetchTempUnit() - else { return } - updateWeatherInfo(listInfo: weatherInfo, tempUnit: tempUnit) - updateCityInfo(cityInfo) - updateWeatherIcon(iconName: weatherInfo.weather.icon) - } + } extension WeatherDetailViewController { - func updateWeatherInfo(listInfo: WeatherForecastInfo, tempUnit: TempUnit){ - let date: Date = Date(timeIntervalSince1970: listInfo.dt) - navigationItem.title = dateFormatter.string(from: date) - 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)" - popLabel.text = "강수 확률 : \(listInfo.main.pop * 100)%" - humidityLabel.text = "습도 : \(listInfo.main.humidity)%" + func updateWeatherInfo(listInfo: WeatherDetailInfo, tempUnit: TempUnit){ + + navigationItem.title = listInfo.date + weatherGroupLabel.text = listInfo.mainWeather + weatherDescriptionLabel.text = listInfo.description + temperatureLabel.text = "현재 기온 : \(listInfo.currentTemp)\(tempUnit.expression)" + feelsLikeLabel.text = "체감 기온 : \(listInfo.feelsLikeTemp)\(tempUnit.expression)" + maximumTemperatureLable.text = "최고 기온 : \(listInfo.maxTemp)\(tempUnit.expression)" + minimumTemperatureLable.text = "최저 기온 : \(listInfo.minTemp)\(tempUnit.expression)" + popLabel.text = "강수 확률 : \(listInfo.pop * 100)%" + humidityLabel.text = "습도 : \(listInfo.humidity)%" } - func updateWeatherIcon(iconName: String){ - Task { - if let iconImage = await NetworkService.shared.fetchWeatherIconImage(iconName: iconName) { - DispatchQueue.main.async { - self.iconImageView.image = iconImage - } - } + @MainActor + func updateWeatherIcon(iconName: String) async { + if let iconImage = await TransforJSON.shared.fetchWeatherIconImage(iconName: iconName) { + self.iconImageView.image = iconImage } } - func updateCityInfo(_ cityInfo: City) { - 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)))" + func updateCityInfo(_ cityInfo: CityDetailInfo) { + sunriseTimeLabel.text = "일출 : \(cityInfo.sunrise)" + sunsetTimeLabel.text = "일몰 : \(cityInfo.sunset)" } } diff --git a/WeatherForecast/WeatherForecast/ViewController.swift b/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift similarity index 51% rename from WeatherForecast/WeatherForecast/ViewController.swift rename to WeatherForecast/WeatherForecast/WeatherForecastViewController.swift index d6674cc..cd36a2a 100644 --- a/WeatherForecast/WeatherForecast/ViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift @@ -6,20 +6,25 @@ import UIKit -final class ViewController: UIViewController { +final class WeatherForecastViewController: UIViewController { var tableView: UITableView! let refreshControl: UIRefreshControl = UIRefreshControl() var weatherJSON: WeatherJSON? var icons: [UIImage]? var tempUnit: TempUnit = .metric - var selectIndex : IndexPath! + 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() } } -extension ViewController { +extension WeatherForecastViewController { @objc private func changeTempUnit() { switch tempUnit { case .imperial: @@ -69,14 +74,14 @@ extension ViewController { } } -extension ViewController { +extension WeatherForecastViewController { private func loadJSON() { - weatherJSON = NetworkService.shared.fetchWeatherJSON(weatherInfo: weatherJSON) + weatherJSON = TransforJSON.shared.fetchWeatherJSON(weatherInfo: weatherJSON) navigationItem.title = weatherJSON?.city.name } } -extension ViewController: UITableViewDataSource { +extension WeatherForecastViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { 1 @@ -92,30 +97,78 @@ extension ViewController: UITableViewDataSource { let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] else { return cell } - cell.configure(info: weatherForecastInfo, tempUnit: tempUnit) + let weatherDetailInfo = WeatherDetailInfo(weatherForecast: weatherForecastInfo) + cell.configure(info: weatherDetailInfo, tempUnit: tempUnit) return cell } } -extension ViewController: UITableViewDelegate { +extension WeatherForecastViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - selectIndex = indexPath - let detailViewController: WeatherDetailViewController = WeatherDetailViewController() - detailViewController.delegate = self + guard let weatherForecast = weatherJSON?.weatherForecast[indexPath.row],let cityInfo = weatherJSON?.city else {return} + + let weatherDetailInfo = WeatherDetailInfo( + weatherForecast: weatherForecast) + let cityDetailInfo = CityDetailInfo(city: cityInfo) + + let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherInfo: weatherDetailInfo, cityInfo: cityDetailInfo, tempUnit: tempUnit) navigationController?.show(detailViewController, sender: self) } + } - -extension ViewController: WeatherForeCastDelegate{ - - func fetchWeatherInfo() -> WeatherForecastInfo { - return (NetworkService.shared.fetchWeatherJSON(weatherInfo: weatherJSON)?.weatherForecast[selectIndex.row])! // 선택한 셀의 정보 넘기기 +struct WeatherDetailInfo { + 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 weatherForecast: WeatherForecastInfo + var mainWeather: String + var currentTemp: Double + var feelsLikeTemp: Double + var maxTemp: Double + var minTemp: Double + var pop: Double + var humidity: Double + var description: String + var date: String + var iconImageUrl: String + + init(weatherForecast: WeatherForecastInfo) { + self.weatherForecast = weatherForecast + self.mainWeather = weatherForecast.weather.main + self.currentTemp = weatherForecast.main.temp + self.feelsLikeTemp = weatherForecast.main.feelsLike + self.maxTemp = weatherForecast.main.tempMax + self.minTemp = weatherForecast.main.tempMin + self.pop = weatherForecast.main.pop + self.humidity = weatherForecast.main.humidity + self.description = weatherForecast.weather.description + self.date = dateFormatter.string(from: Date(timeIntervalSince1970: weatherForecast.dt)) + self.iconImageUrl = weatherForecast.weather.icon + } +} +struct CityDetailInfo { + + let formatter: DateFormatter = { + let formatter: DateFormatter = DateFormatter() + formatter.dateFormat = .none + formatter.timeStyle = .short + formatter.locale = .init(identifier: "ko_KR") + return formatter + }() + + var city: City + var sunrise: String { + return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunrise)))" } - func fetchCityInfo() -> City { - return (NetworkService.shared.fetchWeatherJSON(weatherInfo: weatherJSON)?.city)! + var sunset: String { + return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunset)))" } - func fetchTempUnit() -> TempUnit { - return tempUnit + init(city: City) { + self.city = city } } diff --git a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift index 645c40e..987ffc3 100644 --- a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift @@ -108,20 +108,19 @@ class WeatherTableViewCell: UITableViewCell { weatherLabel.text = "~~~" descriptionLabel.text = "~~~~~" } - func configure(info: WeatherForecastInfo,tempUnit : TempUnit){ - weatherLabel.text = info.weather.main - descriptionLabel.text = info.weather.description - temperatureLabel.text = "\(info.main.temp)\(tempUnit.expression)" - let date: Date = Date(timeIntervalSince1970: info.dt) - dateLabel.text = dateFormatter.string(from: date) - loadImage(info.weather.icon) + func configure(info: WeatherDetailInfo,tempUnit: TempUnit){ + weatherLabel.text = info.mainWeather + descriptionLabel.text = info.description + temperatureLabel.text = "\(info.currentTemp)\(tempUnit.expression)" + dateLabel.text = info.date + loadImage(info.iconImageUrl) } } extension WeatherTableViewCell { func loadImage(_ iconName: String){ Task { - if let iconImage = await NetworkService.shared.fetchWeatherIconImage(iconName: iconName) { + if let iconImage = await TransforJSON.shared.fetchWeatherIconImage(iconName: iconName) { DispatchQueue.main.async { self.weatherIcon.image = iconImage } From 8fbd25d0537983469bc830403876950b83daec29 Mon Sep 17 00:00:00 2001 From: gadisom Date: Wed, 7 Feb 2024 07:29:42 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Add=20cache=20=EA=B0=9D=EC=B2=B4=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 --- .../WeatherForecast.xcodeproj/project.pbxproj | 8 +- .../WeatherForecast/InfoModel.swift | 86 +++++++++++++++++++ .../WeatherForecast/Protocol.swift | 14 --- .../WeatherForecast/TransforJSON.swift | 34 +++++++- .../WeatherDetailViewController.swift | 20 +++-- .../WeatherForecastViewController.swift | 61 +------------ .../WeatherTableViewCell.swift | 21 ++--- 7 files changed, 147 insertions(+), 97 deletions(-) create mode 100644 WeatherForecast/WeatherForecast/InfoModel.swift delete mode 100644 WeatherForecast/WeatherForecast/Protocol.swift diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index fe4e46a..c0143dd 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -17,7 +17,7 @@ C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */; }; C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */; }; FFC9B6D92B6FB0DD00BBFBE1 /* TransforJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */; }; - FFC9B6DB2B6FB11F00BBFBE1 /* Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */; }; + FFD4BC672B72E19D008F83F1 /* InfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD4BC662B72E19D008F83F1 /* InfoModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -33,7 +33,7 @@ C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTableViewCell.swift; sourceTree = ""; }; C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherDetailViewController.swift; sourceTree = ""; }; FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransforJSON.swift; sourceTree = ""; }; - FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Protocol.swift; sourceTree = ""; }; + FFD4BC662B72E19D008F83F1 /* InfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -69,7 +69,7 @@ C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */, C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */, C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, - FFC9B6DA2B6FB11F00BBFBE1 /* Protocol.swift */, + FFD4BC662B72E19D008F83F1 /* InfoModel.swift */, FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */, C7743D902B21C38100DF0D09 /* WeatherForecastViewController.swift */, C741F66F2B58F00500A4DDC0 /* Weather.swift */, @@ -156,11 +156,11 @@ C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, C7743D912B21C38100DF0D09 /* WeatherForecastViewController.swift in Sources */, C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, - FFC9B6DB2B6FB11F00BBFBE1 /* Protocol.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */, FFC9B6D92B6FB0DD00BBFBE1 /* TransforJSON.swift in Sources */, + FFD4BC672B72E19D008F83F1 /* InfoModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WeatherForecast/WeatherForecast/InfoModel.swift b/WeatherForecast/WeatherForecast/InfoModel.swift new file mode 100644 index 0000000..20498d8 --- /dev/null +++ b/WeatherForecast/WeatherForecast/InfoModel.swift @@ -0,0 +1,86 @@ +// +// InfoModel.swift +// WeatherForecast +// +// Created by 김정원 on 2/7/24. +// + +import Foundation + +struct WeatherDetailInfo { + 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 weatherForecast: WeatherForecastInfo + + var mainWeather: String { + return weatherForecast.weather.main + } + var description: String { + return weatherForecast.weather.description + } + var date: String { + return dateFormatter.string(from: Date(timeIntervalSince1970: weatherForecast.dt)) + } + var iconImageUrl: String { + weatherForecast.weather.icon + } + + init(weatherForecast: WeatherForecastInfo) { + self.weatherForecast = weatherForecast + } +} +struct MainDetailInfo { + + var mainInfo: MainInfo + + var currentTemp: Double { + return mainInfo.temp + } + var feelsLikeTemp: Double { + return mainInfo.feelsLike + } + var maxTemp: Double { + return mainInfo.tempMax + } + var minTemp: Double { + return mainInfo.tempMin + } + var pop: Double { + return mainInfo.pop + } + var humidity: Double { + return mainInfo.humidity + } + + init(mainInfo: MainInfo){ + self.mainInfo = mainInfo + } + +} +struct CityDetailInfo { + + let formatter: DateFormatter = { + let formatter: DateFormatter = DateFormatter() + formatter.dateFormat = .none + formatter.timeStyle = .short + formatter.locale = .init(identifier: "ko_KR") + return formatter + }() + + var city: City + + var sunrise: String { + return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunrise)))" + } + var sunset: String { + return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunset)))" + } + init(city: City) { + self.city = city + } +} diff --git a/WeatherForecast/WeatherForecast/Protocol.swift b/WeatherForecast/WeatherForecast/Protocol.swift deleted file mode 100644 index 9c6cbc1..0000000 --- a/WeatherForecast/WeatherForecast/Protocol.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Protocol.swift -// WeatherForecast -// -// Created by 김정원 on 2/4/24. -// - -import Foundation - -protocol WeatherForeCastDelegate: AnyObject { - func fetchWeatherInfo() -> WeatherForecastInfo - func fetchCityInfo() -> City - func fetchTempUnit() -> TempUnit -} diff --git a/WeatherForecast/WeatherForecast/TransforJSON.swift b/WeatherForecast/WeatherForecast/TransforJSON.swift index 1c70a0f..e75eadc 100644 --- a/WeatherForecast/WeatherForecast/TransforJSON.swift +++ b/WeatherForecast/WeatherForecast/TransforJSON.swift @@ -26,14 +26,46 @@ final class TransforJSON { } func fetchWeatherIconImage(iconName: String) async -> UIImage? { let urlString = "https://openweathermap.org/img/wn/\(iconName)@2x.png" + let cacheKey = NSString(string: urlString) + + if let cachedImage = ImageCacheManager.shared.getImage(forKey: urlString) { + return cachedImage + } guard let url = URL(string: urlString) else { return nil } do { let (data, _) = try await URLSession.shared.data(from: url) - return UIImage(data: data) + guard let image = UIImage(data: data) else { return nil } + + ImageCacheManager.shared.cacheImage(image, forKey: urlString) + + return image } catch { print("Error fetching weather icon: \(error)") return nil } } + +} +class ImageCacheManager { + static let shared = ImageCacheManager() // 싱글턴 인스턴스 + private var imageCache: NSCache = NSCache() + + func cacheImage(_ image: UIImage, forKey key: String) { + imageCache.setObject(image, forKey: key as NSString) + log("Image cached for key: \(key)") + } + + func getImage(forKey key: String) -> UIImage? { + if let cachedImage = imageCache.object(forKey: key as NSString) { + log("Returning cached image for key: \(key)") + return cachedImage + } + log("No cached image : \(key)") + return nil + } + + private func log(_ message: String) { + print(message) + } } diff --git a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift index 26fb508..0253048 100644 --- a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift @@ -22,6 +22,7 @@ final class WeatherDetailViewController: UIViewController { let spacingView: UIView = .init() var weatherInfo: WeatherDetailInfo var cityInfo: CityDetailInfo + var mainInfo: MainDetailInfo var tempUnit: TempUnit let dateFormatter: DateFormatter = { let formatter: DateFormatter = DateFormatter() @@ -30,8 +31,9 @@ final class WeatherDetailViewController: UIViewController { return formatter }() - init(weatherInfo: WeatherDetailInfo, cityInfo: CityDetailInfo, tempUnit: TempUnit) { + init(weatherInfo: WeatherDetailInfo, mainInfo: MainDetailInfo, cityInfo: CityDetailInfo, tempUnit: TempUnit) { self.weatherInfo = weatherInfo + self.mainInfo = mainInfo self.cityInfo = cityInfo self.tempUnit = tempUnit super.init(nibName: nil, bundle: nil) @@ -44,7 +46,7 @@ final class WeatherDetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() initialSetUp() - updateWeatherInfo(listInfo: weatherInfo, tempUnit: tempUnit) + updateWeatherInfo(listInfo: weatherInfo, mainInfo: mainInfo, tempUnit: tempUnit) updateCityInfo(cityInfo) Task { await updateWeatherIcon(iconName: weatherInfo.iconImageUrl) @@ -107,17 +109,17 @@ final class WeatherDetailViewController: UIViewController { extension WeatherDetailViewController { - func updateWeatherInfo(listInfo: WeatherDetailInfo, tempUnit: TempUnit){ + func updateWeatherInfo(listInfo: WeatherDetailInfo, mainInfo: MainDetailInfo,tempUnit: TempUnit){ navigationItem.title = listInfo.date weatherGroupLabel.text = listInfo.mainWeather weatherDescriptionLabel.text = listInfo.description - temperatureLabel.text = "현재 기온 : \(listInfo.currentTemp)\(tempUnit.expression)" - feelsLikeLabel.text = "체감 기온 : \(listInfo.feelsLikeTemp)\(tempUnit.expression)" - maximumTemperatureLable.text = "최고 기온 : \(listInfo.maxTemp)\(tempUnit.expression)" - minimumTemperatureLable.text = "최저 기온 : \(listInfo.minTemp)\(tempUnit.expression)" - popLabel.text = "강수 확률 : \(listInfo.pop * 100)%" - humidityLabel.text = "습도 : \(listInfo.humidity)%" + temperatureLabel.text = "현재 기온 : \(mainInfo.currentTemp)\(tempUnit.expression)" + feelsLikeLabel.text = "체감 기온 : \(mainInfo.feelsLikeTemp)\(tempUnit.expression)" + maximumTemperatureLable.text = "최고 기온 : \(mainInfo.maxTemp)\(tempUnit.expression)" + minimumTemperatureLable.text = "최저 기온 : \(mainInfo.minTemp)\(tempUnit.expression)" + popLabel.text = "강수 확률 : \(mainInfo.pop * 100)%" + humidityLabel.text = "습도 : \(mainInfo.humidity)%" } @MainActor func updateWeatherIcon(iconName: String) async { diff --git a/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift b/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift index cd36a2a..04ddc44 100644 --- a/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift @@ -98,7 +98,8 @@ extension WeatherForecastViewController: UITableViewDataSource { return cell } let weatherDetailInfo = WeatherDetailInfo(weatherForecast: weatherForecastInfo) - cell.configure(info: weatherDetailInfo, tempUnit: tempUnit) + let mainDetailInfo = MainDetailInfo(mainInfo: weatherForecastInfo.main) + cell.configure(info: weatherDetailInfo, mainInfo: mainDetailInfo, tempUnit: tempUnit) return cell } } @@ -110,65 +111,11 @@ extension WeatherForecastViewController: UITableViewDelegate { let weatherDetailInfo = WeatherDetailInfo( weatherForecast: weatherForecast) + let mainDetailInfo = MainDetailInfo(mainInfo: weatherForecast.main) let cityDetailInfo = CityDetailInfo(city: cityInfo) - let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherInfo: weatherDetailInfo, cityInfo: cityDetailInfo, tempUnit: tempUnit) + let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherInfo: weatherDetailInfo, mainInfo: mainDetailInfo, cityInfo: cityDetailInfo, tempUnit: tempUnit) navigationController?.show(detailViewController, sender: self) } } -struct WeatherDetailInfo { - 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 weatherForecast: WeatherForecastInfo - var mainWeather: String - var currentTemp: Double - var feelsLikeTemp: Double - var maxTemp: Double - var minTemp: Double - var pop: Double - var humidity: Double - var description: String - var date: String - var iconImageUrl: String - - init(weatherForecast: WeatherForecastInfo) { - self.weatherForecast = weatherForecast - self.mainWeather = weatherForecast.weather.main - self.currentTemp = weatherForecast.main.temp - self.feelsLikeTemp = weatherForecast.main.feelsLike - self.maxTemp = weatherForecast.main.tempMax - self.minTemp = weatherForecast.main.tempMin - self.pop = weatherForecast.main.pop - self.humidity = weatherForecast.main.humidity - self.description = weatherForecast.weather.description - self.date = dateFormatter.string(from: Date(timeIntervalSince1970: weatherForecast.dt)) - self.iconImageUrl = weatherForecast.weather.icon - } -} -struct CityDetailInfo { - - let formatter: DateFormatter = { - let formatter: DateFormatter = DateFormatter() - formatter.dateFormat = .none - formatter.timeStyle = .short - formatter.locale = .init(identifier: "ko_KR") - return formatter - }() - - var city: City - var sunrise: String { - return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunrise)))" - } - var sunset: String { - return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunset)))" - } - init(city: City) { - self.city = city - } -} diff --git a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift index 987ffc3..f1fc0ad 100644 --- a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift @@ -12,7 +12,6 @@ class WeatherTableViewCell: UITableViewCell { var temperatureLabel: UILabel! var weatherLabel: UILabel! var descriptionLabel: UILabel! - var imageChache: NSCache = NSCache() let dateFormatter: DateFormatter = { let formatter: DateFormatter = DateFormatter() @@ -108,23 +107,21 @@ class WeatherTableViewCell: UITableViewCell { weatherLabel.text = "~~~" descriptionLabel.text = "~~~~~" } - func configure(info: WeatherDetailInfo,tempUnit: TempUnit){ + func configure(info: WeatherDetailInfo, mainInfo: MainDetailInfo, tempUnit: TempUnit){ weatherLabel.text = info.mainWeather descriptionLabel.text = info.description - temperatureLabel.text = "\(info.currentTemp)\(tempUnit.expression)" + temperatureLabel.text = "\(mainInfo.currentTemp)\(tempUnit.expression)" dateLabel.text = info.date - loadImage(info.iconImageUrl) + Task { + await loadImage(info.iconImageUrl) + } } } extension WeatherTableViewCell { - - func loadImage(_ iconName: String){ - Task { - if let iconImage = await TransforJSON.shared.fetchWeatherIconImage(iconName: iconName) { - DispatchQueue.main.async { - self.weatherIcon.image = iconImage - } - } + @MainActor + func loadImage(_ iconName: String) async{ + if let iconImage = await TransforJSON.shared.fetchWeatherIconImage(iconName: iconName) { + self.weatherIcon.image = iconImage } } } From 0384790d065d47434fd4e8fa58450ad14121dad7 Mon Sep 17 00:00:00 2001 From: iOS-jeongwon Date: Wed, 7 Feb 2024 10:13:50 +0900 Subject: [PATCH 6/7] [STEP1] Refactor --- WeatherForecast/WeatherForecast/TransforJSON.swift | 2 +- .../WeatherForecast/WeatherForecastViewController.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WeatherForecast/WeatherForecast/TransforJSON.swift b/WeatherForecast/WeatherForecast/TransforJSON.swift index e75eadc..e0e0309 100644 --- a/WeatherForecast/WeatherForecast/TransforJSON.swift +++ b/WeatherForecast/WeatherForecast/TransforJSON.swift @@ -9,7 +9,7 @@ final class TransforJSON { static let shared = TransforJSON() private init() {} - func fetchWeatherJSON(weatherInfo: WeatherJSON?) -> WeatherJSON?{ + func fetchWeatherJSON() -> WeatherJSON?{ let jsonDecoder = JSONDecoder() jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase guard let data = NSDataAsset(name: "weather")?.data else { diff --git a/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift b/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift index 04ddc44..fee4664 100644 --- a/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift @@ -76,7 +76,7 @@ extension WeatherForecastViewController { extension WeatherForecastViewController { private func loadJSON() { - weatherJSON = TransforJSON.shared.fetchWeatherJSON(weatherInfo: weatherJSON) + weatherJSON = TransforJSON.shared.fetchWeatherJSON() navigationItem.title = weatherJSON?.city.name } } From d55947b9471ad82261ec9207fe44bab588684c66 Mon Sep 17 00:00:00 2001 From: iOS-jeongwon Date: Thu, 8 Feb 2024 18:33:14 +0900 Subject: [PATCH 7/7] =?UTF-8?q?Refactor=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherForecast.xcodeproj/project.pbxproj | 52 +++++++++-- .../{ => App}/AppDelegate.swift | 0 .../{ => App}/SceneDelegate.swift | 0 .../{ => Extension }/TransforJSON.swift | 23 +---- .../WeatherForecast/Extension /Utils.swift | 63 ++++++++++++++ .../WeatherForecast/InfoModel.swift | 86 ------------------- .../WeatherData/InfoModel.swift | 27 ++++++ .../{ => WeatherData}/Weather.swift | 4 + .../WeatherDetailViewController.swift | 58 ++++++------- .../WeatherForecastViewController.swift | 14 ++- .../WeatherTableViewCell.swift | 27 ++---- 11 files changed, 177 insertions(+), 177 deletions(-) rename WeatherForecast/WeatherForecast/{ => App}/AppDelegate.swift (100%) rename WeatherForecast/WeatherForecast/{ => App}/SceneDelegate.swift (100%) rename WeatherForecast/WeatherForecast/{ => Extension }/TransforJSON.swift (62%) create mode 100644 WeatherForecast/WeatherForecast/Extension /Utils.swift delete mode 100644 WeatherForecast/WeatherForecast/InfoModel.swift create mode 100644 WeatherForecast/WeatherForecast/WeatherData/InfoModel.swift rename WeatherForecast/WeatherForecast/{ => WeatherData}/Weather.swift (97%) rename WeatherForecast/WeatherForecast/{ => WeatherView}/WeatherDetailViewController.swift (71%) rename WeatherForecast/WeatherForecast/{ => WeatherView}/WeatherForecastViewController.swift (86%) rename WeatherForecast/WeatherForecast/{ => WeatherView}/WeatherTableViewCell.swift (80%) diff --git a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj index c0143dd..98c99f0 100644 --- a/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj +++ b/WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 9995F0702B74AB48007D1105 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9995F06F2B74AB48007D1105 /* Utils.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 */; }; @@ -21,6 +22,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 9995F06F2B74AB48007D1105 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.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 = ""; }; @@ -47,6 +49,43 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 9995F06B2B745A8C007D1105 /* App */ = { + isa = PBXGroup; + children = ( + C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */, + C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */, + ); + path = App; + sourceTree = ""; + }; + 9995F06C2B745AAA007D1105 /* WeatherData */ = { + isa = PBXGroup; + children = ( + FFD4BC662B72E19D008F83F1 /* InfoModel.swift */, + C741F66F2B58F00500A4DDC0 /* Weather.swift */, + ); + path = WeatherData; + sourceTree = ""; + }; + 9995F06D2B745AF9007D1105 /* Extension */ = { + isa = PBXGroup; + children = ( + FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */, + 9995F06F2B74AB48007D1105 /* Utils.swift */, + ); + path = "Extension "; + sourceTree = ""; + }; + 9995F06E2B745B1D007D1105 /* WeatherView */ = { + isa = PBXGroup; + children = ( + C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, + C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, + C7743D902B21C38100DF0D09 /* WeatherForecastViewController.swift */, + ); + path = WeatherView; + sourceTree = ""; + }; C7743D802B21C38100DF0D09 = { isa = PBXGroup; children = ( @@ -66,14 +105,10 @@ C7743D8B2B21C38100DF0D09 /* WeatherForecast */ = { isa = PBXGroup; children = ( - C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */, - C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */, - C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */, - FFD4BC662B72E19D008F83F1 /* InfoModel.swift */, - FFC9B6D82B6FB0DD00BBFBE1 /* TransforJSON.swift */, - C7743D902B21C38100DF0D09 /* WeatherForecastViewController.swift */, - C741F66F2B58F00500A4DDC0 /* Weather.swift */, - C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */, + 9995F06B2B745A8C007D1105 /* App */, + 9995F06E2B745B1D007D1105 /* WeatherView */, + 9995F06D2B745AF9007D1105 /* Extension */, + 9995F06C2B745AAA007D1105 /* WeatherData */, C7743D922B21C38100DF0D09 /* Main.storyboard */, C7743D952B21C38200DF0D09 /* Assets.xcassets */, C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */, @@ -155,6 +190,7 @@ files = ( C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */, C7743D912B21C38100DF0D09 /* WeatherForecastViewController.swift in Sources */, + 9995F0702B74AB48007D1105 /* Utils.swift in Sources */, C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */, C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */, C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */, diff --git a/WeatherForecast/WeatherForecast/AppDelegate.swift b/WeatherForecast/WeatherForecast/App/AppDelegate.swift similarity index 100% rename from WeatherForecast/WeatherForecast/AppDelegate.swift rename to WeatherForecast/WeatherForecast/App/AppDelegate.swift diff --git a/WeatherForecast/WeatherForecast/SceneDelegate.swift b/WeatherForecast/WeatherForecast/App/SceneDelegate.swift similarity index 100% rename from WeatherForecast/WeatherForecast/SceneDelegate.swift rename to WeatherForecast/WeatherForecast/App/SceneDelegate.swift diff --git a/WeatherForecast/WeatherForecast/TransforJSON.swift b/WeatherForecast/WeatherForecast/Extension /TransforJSON.swift similarity index 62% rename from WeatherForecast/WeatherForecast/TransforJSON.swift rename to WeatherForecast/WeatherForecast/Extension /TransforJSON.swift index e0e0309..b4efa39 100644 --- a/WeatherForecast/WeatherForecast/TransforJSON.swift +++ b/WeatherForecast/WeatherForecast/Extension /TransforJSON.swift @@ -24,30 +24,9 @@ final class TransforJSON { } return info } - func fetchWeatherIconImage(iconName: String) async -> UIImage? { - let urlString = "https://openweathermap.org/img/wn/\(iconName)@2x.png" - let cacheKey = NSString(string: urlString) - - if let cachedImage = ImageCacheManager.shared.getImage(forKey: urlString) { - return cachedImage - } - guard let url = URL(string: urlString) else { return nil } - - do { - let (data, _) = try await URLSession.shared.data(from: url) - guard let image = UIImage(data: data) else { return nil } - - ImageCacheManager.shared.cacheImage(image, forKey: urlString) - - return image - } catch { - print("Error fetching weather icon: \(error)") - return nil - } - } } -class ImageCacheManager { +final class ImageCacheManager { static let shared = ImageCacheManager() // 싱글턴 인스턴스 private var imageCache: NSCache = NSCache() diff --git a/WeatherForecast/WeatherForecast/Extension /Utils.swift b/WeatherForecast/WeatherForecast/Extension /Utils.swift new file mode 100644 index 0000000..aa49d12 --- /dev/null +++ b/WeatherForecast/WeatherForecast/Extension /Utils.swift @@ -0,0 +1,63 @@ +// +// Utils.swift +// WeatherForecast +// +// Created by 김정원 on 2/8/24. +// + +import Foundation +import UIKit + +//final class Utils { +// +// static func formatDate(date: Date) +// +//} + +extension DateFormatter { + static let koreanTimeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = .none + formatter.timeStyle = .short + formatter.locale = Locale(identifier: "ko_KR") + return formatter + }() + static let date: DateFormatter = { + let formatter: DateFormatter = DateFormatter() + formatter.locale = .init(identifier: "ko_KR") + formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm" + return formatter + }() + +} +extension Collection { + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} + +extension UIImageView { + + static func fetchImage(iconName: String) async -> UIImage? { + let iconImage = "https://openweathermap.org/img/wn/\(iconName)@2x.png" + let cacheKey = NSString(string: iconImage) + + if let cachedImage = ImageCacheManager.shared.getImage(forKey: iconImage) { + return cachedImage + + } + guard let url = URL(string: iconImage) else {return nil } + do { + let(data, _) = try await URLSession.shared.data(from: url) + guard let image = UIImage(data: data) else { + return nil + } + ImageCacheManager.shared.cacheImage(image, forKey: iconImage) + return image + } catch { + print("Error fetching weather icon: \(error)") + return nil + } + } + +} diff --git a/WeatherForecast/WeatherForecast/InfoModel.swift b/WeatherForecast/WeatherForecast/InfoModel.swift deleted file mode 100644 index 20498d8..0000000 --- a/WeatherForecast/WeatherForecast/InfoModel.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// InfoModel.swift -// WeatherForecast -// -// Created by 김정원 on 2/7/24. -// - -import Foundation - -struct WeatherDetailInfo { - 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 weatherForecast: WeatherForecastInfo - - var mainWeather: String { - return weatherForecast.weather.main - } - var description: String { - return weatherForecast.weather.description - } - var date: String { - return dateFormatter.string(from: Date(timeIntervalSince1970: weatherForecast.dt)) - } - var iconImageUrl: String { - weatherForecast.weather.icon - } - - init(weatherForecast: WeatherForecastInfo) { - self.weatherForecast = weatherForecast - } -} -struct MainDetailInfo { - - var mainInfo: MainInfo - - var currentTemp: Double { - return mainInfo.temp - } - var feelsLikeTemp: Double { - return mainInfo.feelsLike - } - var maxTemp: Double { - return mainInfo.tempMax - } - var minTemp: Double { - return mainInfo.tempMin - } - var pop: Double { - return mainInfo.pop - } - var humidity: Double { - return mainInfo.humidity - } - - init(mainInfo: MainInfo){ - self.mainInfo = mainInfo - } - -} -struct CityDetailInfo { - - let formatter: DateFormatter = { - let formatter: DateFormatter = DateFormatter() - formatter.dateFormat = .none - formatter.timeStyle = .short - formatter.locale = .init(identifier: "ko_KR") - return formatter - }() - - var city: City - - var sunrise: String { - return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunrise)))" - } - var sunset: String { - return "\(formatter.string(from: Date(timeIntervalSince1970: city.sunset)))" - } - init(city: City) { - self.city = city - } -} diff --git a/WeatherForecast/WeatherForecast/WeatherData/InfoModel.swift b/WeatherForecast/WeatherForecast/WeatherData/InfoModel.swift new file mode 100644 index 0000000..65e897b --- /dev/null +++ b/WeatherForecast/WeatherForecast/WeatherData/InfoModel.swift @@ -0,0 +1,27 @@ +// +// InfoModel.swift +// WeatherForecast +// +// Created by 김정원 on 2/7/24. +// + +import Foundation +import UIKit + +struct CityDetailInfo { + + var city: City + + var sunrise: String { + return DateFormatter.koreanTimeFormatter.string(from: Date(timeIntervalSince1970: city.sunrise)) + } + + var sunset: String { + return DateFormatter.koreanTimeFormatter.string(from: Date(timeIntervalSince1970: city.sunset)) + } + + init(city: City) { + self.city = city + } +} + diff --git a/WeatherForecast/WeatherForecast/Weather.swift b/WeatherForecast/WeatherForecast/WeatherData/Weather.swift similarity index 97% rename from WeatherForecast/WeatherForecast/Weather.swift rename to WeatherForecast/WeatherForecast/WeatherData/Weather.swift index 6d35919..466a87a 100644 --- a/WeatherForecast/WeatherForecast/Weather.swift +++ b/WeatherForecast/WeatherForecast/WeatherData/Weather.swift @@ -59,3 +59,7 @@ enum TempUnit: String { } } } + +protocol tempStrategy { + //var +} diff --git a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift b/WeatherForecast/WeatherForecast/WeatherView/WeatherDetailViewController.swift similarity index 71% rename from WeatherForecast/WeatherForecast/WeatherDetailViewController.swift rename to WeatherForecast/WeatherForecast/WeatherView/WeatherDetailViewController.swift index 0253048..db37bbc 100644 --- a/WeatherForecast/WeatherForecast/WeatherDetailViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherView/WeatherDetailViewController.swift @@ -8,30 +8,24 @@ import UIKit final class WeatherDetailViewController: UIViewController { - let iconImageView: UIImageView = .init() - let weatherGroupLabel: UILabel = .init() - let weatherDescriptionLabel: UILabel = .init() - let temperatureLabel: UILabel = .init() - let feelsLikeLabel: UILabel = .init() - let maximumTemperatureLable: UILabel = .init() - let minimumTemperatureLable: UILabel = .init() - let popLabel: UILabel = .init() - let humidityLabel: UILabel = .init() - let sunriseTimeLabel: UILabel = .init() - let sunsetTimeLabel: UILabel = .init() - let spacingView: UIView = .init() - var weatherInfo: WeatherDetailInfo - var cityInfo: CityDetailInfo - var mainInfo: MainDetailInfo - var tempUnit: TempUnit - let dateFormatter: DateFormatter = { - let formatter: DateFormatter = DateFormatter() - formatter.locale = .init(identifier: "ko_KR") - formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm" - return formatter - }() + private let iconImageView: UIImageView = .init() + private let weatherGroupLabel: UILabel = .init() + private let weatherDescriptionLabel: UILabel = .init() + private let temperatureLabel: UILabel = .init() + private let feelsLikeLabel: UILabel = .init() + private let maximumTemperatureLable: UILabel = .init() + private let minimumTemperatureLable: UILabel = .init() + private let popLabel: UILabel = .init() + private let humidityLabel: UILabel = .init() + private let sunriseTimeLabel: UILabel = .init() + private let sunsetTimeLabel: UILabel = .init() + private let spacingView: UIView = .init() + private let weatherInfo: WeatherForecastInfo + private let cityInfo: CityDetailInfo + private let mainInfo: MainInfo + private let tempUnit: TempUnit - init(weatherInfo: WeatherDetailInfo, mainInfo: MainDetailInfo, cityInfo: CityDetailInfo, tempUnit: TempUnit) { + init(weatherInfo: WeatherForecastInfo, mainInfo: MainInfo, cityInfo: CityDetailInfo, tempUnit: TempUnit) { self.weatherInfo = weatherInfo self.mainInfo = mainInfo self.cityInfo = cityInfo @@ -49,7 +43,7 @@ final class WeatherDetailViewController: UIViewController { updateWeatherInfo(listInfo: weatherInfo, mainInfo: mainInfo, tempUnit: tempUnit) updateCityInfo(cityInfo) Task { - await updateWeatherIcon(iconName: weatherInfo.iconImageUrl) + await updateWeatherIcon(iconName: weatherInfo.weather.icon) } } @@ -109,15 +103,15 @@ final class WeatherDetailViewController: UIViewController { extension WeatherDetailViewController { - func updateWeatherInfo(listInfo: WeatherDetailInfo, mainInfo: MainDetailInfo,tempUnit: TempUnit){ + func updateWeatherInfo(listInfo: WeatherForecastInfo, mainInfo: MainInfo,tempUnit: TempUnit){ - navigationItem.title = listInfo.date - weatherGroupLabel.text = listInfo.mainWeather - weatherDescriptionLabel.text = listInfo.description - temperatureLabel.text = "현재 기온 : \(mainInfo.currentTemp)\(tempUnit.expression)" - feelsLikeLabel.text = "체감 기온 : \(mainInfo.feelsLikeTemp)\(tempUnit.expression)" - maximumTemperatureLable.text = "최고 기온 : \(mainInfo.maxTemp)\(tempUnit.expression)" - minimumTemperatureLable.text = "최저 기온 : \(mainInfo.minTemp)\(tempUnit.expression)" + navigationItem.title = listInfo.dtTxt + weatherGroupLabel.text = listInfo.weather.main + weatherDescriptionLabel.text = listInfo.weather.description + temperatureLabel.text = "현재 기온 : \(mainInfo.temp)\(tempUnit.expression)" + feelsLikeLabel.text = "체감 기온 : \(mainInfo.feelsLike)\(tempUnit.expression)" + maximumTemperatureLable.text = "최고 기온 : \(mainInfo.tempMax)\(tempUnit.expression)" + minimumTemperatureLable.text = "최저 기온 : \(mainInfo.tempMin)\(tempUnit.expression)" popLabel.text = "강수 확률 : \(mainInfo.pop * 100)%" humidityLabel.text = "습도 : \(mainInfo.humidity)%" } diff --git a/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift b/WeatherForecast/WeatherForecast/WeatherView/WeatherForecastViewController.swift similarity index 86% rename from WeatherForecast/WeatherForecast/WeatherForecastViewController.swift rename to WeatherForecast/WeatherForecast/WeatherView/WeatherForecastViewController.swift index fee4664..9ec5091 100644 --- a/WeatherForecast/WeatherForecast/WeatherForecastViewController.swift +++ b/WeatherForecast/WeatherForecast/WeatherView/WeatherForecastViewController.swift @@ -97,9 +97,9 @@ extension WeatherForecastViewController: UITableViewDataSource { let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] else { return cell } - let weatherDetailInfo = WeatherDetailInfo(weatherForecast: weatherForecastInfo) - let mainDetailInfo = MainDetailInfo(mainInfo: weatherForecastInfo.main) - cell.configure(info: weatherDetailInfo, mainInfo: mainDetailInfo, tempUnit: tempUnit) + + let mainInfo = weatherForecastInfo.main + cell.configure(info: weatherForecastInfo, mainInfo: mainInfo, tempUnit: tempUnit) return cell } } @@ -107,14 +107,12 @@ extension WeatherForecastViewController: UITableViewDataSource { extension WeatherForecastViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - guard let weatherForecast = weatherJSON?.weatherForecast[indexPath.row],let cityInfo = weatherJSON?.city else {return} + guard let weatherForecast = weatherJSON?.weatherForecast[safe: indexPath.row],let cityInfo = weatherJSON?.city else {return} - let weatherDetailInfo = WeatherDetailInfo( - weatherForecast: weatherForecast) - let mainDetailInfo = MainDetailInfo(mainInfo: weatherForecast.main) + let mainDetailInfo = weatherForecast.main let cityDetailInfo = CityDetailInfo(city: cityInfo) - let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherInfo: weatherDetailInfo, mainInfo: mainDetailInfo, cityInfo: cityDetailInfo, tempUnit: tempUnit) + let detailViewController: WeatherDetailViewController = WeatherDetailViewController(weatherInfo: weatherForecast, mainInfo: mainDetailInfo, cityInfo: cityDetailInfo, tempUnit: tempUnit) navigationController?.show(detailViewController, sender: self) } diff --git a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift b/WeatherForecast/WeatherForecast/WeatherView/WeatherTableViewCell.swift similarity index 80% rename from WeatherForecast/WeatherForecast/WeatherTableViewCell.swift rename to WeatherForecast/WeatherForecast/WeatherView/WeatherTableViewCell.swift index f1fc0ad..f4c07ef 100644 --- a/WeatherForecast/WeatherForecast/WeatherTableViewCell.swift +++ b/WeatherForecast/WeatherForecast/WeatherView/WeatherTableViewCell.swift @@ -12,13 +12,6 @@ class WeatherTableViewCell: UITableViewCell { var temperatureLabel: UILabel! var weatherLabel: UILabel! var descriptionLabel: UILabel! - - 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 init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -107,21 +100,13 @@ class WeatherTableViewCell: UITableViewCell { weatherLabel.text = "~~~" descriptionLabel.text = "~~~~~" } - func configure(info: WeatherDetailInfo, mainInfo: MainDetailInfo, tempUnit: TempUnit){ - weatherLabel.text = info.mainWeather - descriptionLabel.text = info.description - temperatureLabel.text = "\(mainInfo.currentTemp)\(tempUnit.expression)" - dateLabel.text = info.date + func configure(info: WeatherForecastInfo, mainInfo: MainInfo, tempUnit: TempUnit){ + weatherLabel.text = info.weather.main + descriptionLabel.text = info.weather.description + temperatureLabel.text = "\(mainInfo.temp)\(tempUnit.expression)" + dateLabel.text = info.dtTxt Task { - await loadImage(info.iconImageUrl) - } - } -} -extension WeatherTableViewCell { - @MainActor - func loadImage(_ iconName: String) async{ - if let iconImage = await TransforJSON.shared.fetchWeatherIconImage(iconName: iconName) { - self.weatherIcon.image = iconImage + weatherIcon.image = await UIImageView.fetchImage(iconName: info.weather.icon) } } }