diff --git a/Weather-App/Weather-App/AppDelegate.swift b/Weather-App/Weather-App/AppDelegate.swift index 1c0f960..e518dac 100644 --- a/Weather-App/Weather-App/AppDelegate.swift +++ b/Weather-App/Weather-App/AppDelegate.swift @@ -13,7 +13,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + + /* ========= 폰트 이름 확인 ========= + for family in UIFont.familyNames { + // 폰트 패밀리 이름 + print(family) + // 각 폰트 이름 + for names in UIFont.fontNames(forFamilyName: family) { + print("== \(names)") + } + } + =============================== */ + return true } diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/Contents.json b/Weather-App/Weather-App/Assets.xcassets/bars/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar1.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/bars/bar1.imageset/Contents.json new file mode 100644 index 0000000..64469ff --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bar1.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar1.imageset/bar1.svg b/Weather-App/Weather-App/Assets.xcassets/bars/bar1.imageset/bar1.svg new file mode 100644 index 0000000..ca082e1 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar1.imageset/bar1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar2.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/bars/bar2.imageset/Contents.json new file mode 100644 index 0000000..4509b8a --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bar2.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar2.imageset/bar2.svg b/Weather-App/Weather-App/Assets.xcassets/bars/bar2.imageset/bar2.svg new file mode 100644 index 0000000..3da9742 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar2.imageset/bar2.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar3.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/bars/bar3.imageset/Contents.json new file mode 100644 index 0000000..62bf09e --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bar3.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar3.imageset/bar3.svg b/Weather-App/Weather-App/Assets.xcassets/bars/bar3.imageset/bar3.svg new file mode 100644 index 0000000..46862b2 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar3.imageset/bar3.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar4.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/bars/bar4.imageset/Contents.json new file mode 100644 index 0000000..63fd5a0 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bar4.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar4.imageset/bar4.svg b/Weather-App/Weather-App/Assets.xcassets/bars/bar4.imageset/bar4.svg new file mode 100644 index 0000000..90fa37f --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar4.imageset/bar4.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar5.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/bars/bar5.imageset/Contents.json new file mode 100644 index 0000000..3e10651 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bar5.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar5.imageset/bar5.svg b/Weather-App/Weather-App/Assets.xcassets/bars/bar5.imageset/bar5.svg new file mode 100644 index 0000000..acb7e49 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar5.imageset/bar5.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar6.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/bars/bar6.imageset/Contents.json new file mode 100644 index 0000000..b37d570 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bar6.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/bars/bar6.imageset/bar6.svg b/Weather-App/Weather-App/Assets.xcassets/bars/bar6.imageset/bar6.svg new file mode 100644 index 0000000..2326c92 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/bars/bar6.imageset/bar6.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/icons/calendar.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/icons/calendar.imageset/Contents.json new file mode 100644 index 0000000..14f6363 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/icons/calendar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "calendar.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/icons/calendar.imageset/calendar.svg b/Weather-App/Weather-App/Assets.xcassets/icons/calendar.imageset/calendar.svg new file mode 100644 index 0000000..8bbd88f --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/icons/calendar.imageset/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/Contents.json index 00d2a45..1fb746a 100644 --- a/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/Contents.json +++ b/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Frame 8.svg", + "filename" : "search.svg", "idiom" : "universal", "scale" : "1x" }, diff --git a/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/Frame 8.svg b/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/Frame 8.svg deleted file mode 100644 index b100ed1..0000000 --- a/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/Frame 8.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/search.svg b/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/search.svg new file mode 100644 index 0000000..b66dd1f --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/icons/icon-search.imageset/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/icons/pageIcon.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/icons/pageIcon.imageset/Contents.json new file mode 100644 index 0000000..484dbac --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/icons/pageIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "pageIcon.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Weather-App/Weather-App/Assets.xcassets/icons/pageIcon.imageset/pageIcon.svg b/Weather-App/Weather-App/Assets.xcassets/icons/pageIcon.imageset/pageIcon.svg new file mode 100644 index 0000000..6b78f87 --- /dev/null +++ b/Weather-App/Weather-App/Assets.xcassets/icons/pageIcon.imageset/pageIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/Contents.json b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/Contents.json index 5cbb56e..34d04b2 100644 --- a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/Contents.json +++ b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "bg@1.png", + "filename" : "bg-long.jpg", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "bg@2.png", + "filename" : "bg-long@2.jpg", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "bg@3.png", + "filename" : "bg-long@3.jpg", "idiom" : "universal", "scale" : "3x" } diff --git a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long.jpg b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long.jpg new file mode 100644 index 0000000..ed3928b Binary files /dev/null and b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long.jpg differ diff --git a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long@2.jpg b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long@2.jpg new file mode 100644 index 0000000..c307fd2 Binary files /dev/null and b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long@2.jpg differ diff --git a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long@3.jpg b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long@3.jpg new file mode 100644 index 0000000..1dcc874 Binary files /dev/null and b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg-long@3.jpg differ diff --git a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@1.png b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@1.png deleted file mode 100644 index 27aa323..0000000 Binary files a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@1.png and /dev/null differ diff --git a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@2.png b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@2.png deleted file mode 100644 index a678eb0..0000000 Binary files a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@2.png and /dev/null differ diff --git a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@3.png b/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@3.png deleted file mode 100644 index 9cfe37b..0000000 Binary files a/Weather-App/Weather-App/Assets.xcassets/images/bg.imageset/bg@3.png and /dev/null differ diff --git a/Weather-App/Weather-App/Info.plist b/Weather-App/Weather-App/Info.plist index 08e2c3a..8564edb 100644 --- a/Weather-App/Weather-App/Info.plist +++ b/Weather-App/Weather-App/Info.plist @@ -2,13 +2,15 @@ + UIViewControllerBasedStatusBarAppearance + UIAppFonts - SF-Pro-Text-Thin.otf - SF-Pro-Text-Regular.otf - SF-Pro-Text-Medium.otf SF-Pro-Text-Bold.otf SF-Pro-Text-Light.otf + SF-Pro-Text-Medium.otf + SF-Pro-Text-Regular.otf + SF-Pro-Text-Thin.otf UIApplicationSceneManifest diff --git a/Weather-App/Weather-App/Model/WeatherResponse.swift b/Weather-App/Weather-App/Model/WeatherResponse.swift new file mode 100644 index 0000000..5b9d9f7 --- /dev/null +++ b/Weather-App/Weather-App/Model/WeatherResponse.swift @@ -0,0 +1,24 @@ +// +// Weather.swift +// Weathers +// +// Created by yeonsu on 11/14/23. +// + +import Foundation + +struct WeatherResponse: Codable { + let main: Main + let timezone: Int + let name: String +} + +struct Main: Codable { + let temp: Double? + let temp_min: Double? + let temp_max: Double? + + static var placeholder: Main { + return Main(temp: nil, temp_min: nil, temp_max: nil) + } +} diff --git a/Weather-App/Weather-App/Resources/Data/TodayWeatherData.swift b/Weather-App/Weather-App/Resources/Data/TodayWeatherData.swift new file mode 100644 index 0000000..a47dcf3 --- /dev/null +++ b/Weather-App/Weather-App/Resources/Data/TodayWeatherData.swift @@ -0,0 +1,53 @@ +// +// TodayWeatherData.swift +// Weather-App +// +// Created by yeonsu on 11/8/23. +// + +import Foundation + +struct TodayWeatherData { + let time: String + let weatherImage: String + let hourlyWeather: String + + init(time: String, weatherImage: String, hourlyWeather: String) { + self.time = time + self.weatherImage = weatherImage + self.hourlyWeather = hourlyWeather + } +} + +private let weatherImages: [String] = ["status-blur", "status-drizzle", "status-rain", "status-thunder", "status-shower"] + +var todayWeatherData: [TodayWeatherData] = [.init(time: "Now", + weatherImage: weatherImages[0], + hourlyWeather: "21°"), + .init(time: "10시", + weatherImage: weatherImages[1], + hourlyWeather: "21°"), + .init(time: "11시", + weatherImage: weatherImages[2], + hourlyWeather: "19°"), + .init(time: "12시", + weatherImage: weatherImages[3], + hourlyWeather: "19°"), + .init(time: "1시", + weatherImage: weatherImages[4], + hourlyWeather: "19°"), + .init(time: "2시", + weatherImage: weatherImages[4], + hourlyWeather: "20°"), + .init(time: "3시", + weatherImage: weatherImages[4], + hourlyWeather: "20°"), + .init(time: "4시", + weatherImage: weatherImages[3], + hourlyWeather: "18°"), + .init(time: "5시", + weatherImage: weatherImages[2], + hourlyWeather: "18°"), + .init(time: "6시", + weatherImage: weatherImages[0], + hourlyWeather: "19°")] diff --git a/Weather-App/Weather-App/Resources/Data/WeeklyWeatherData.swift b/Weather-App/Weather-App/Resources/Data/WeeklyWeatherData.swift new file mode 100644 index 0000000..338ae2b --- /dev/null +++ b/Weather-App/Weather-App/Resources/Data/WeeklyWeatherData.swift @@ -0,0 +1,98 @@ +// +// DetailWeatherData.swift +// Weather-App +// +// Created by yeonsu on 11/8/23. +// + +import Foundation + +struct WeeklyWeatherData { + let weekDay: String + let weatherImage: String + let chanceOfPrecipitation: String? + let minimumTemperature: String + let maximumTemperature: String + let temperatureBarImage: String + + init(weekDay: String, weatherImage: String, chanceOfPrecipitation: String?, minimumTemperature: String, temperatureBarImage: String, maximumTemperature: String) { + self.weekDay = weekDay + self.weatherImage = weatherImage + self.chanceOfPrecipitation = chanceOfPrecipitation + self.minimumTemperature = minimumTemperature + self.maximumTemperature = maximumTemperature + self.temperatureBarImage = temperatureBarImage + + } +} + +private let weatherImages: [String] = ["status-blur", "status-drizzle", "status-rain", "status-thunder", "status-shower"] + +private let temperatureBarImages: [String] = ["bar1", "bar2", "bar3", "bar4", "bar5", "bar6"] + +var weeklyWeatherData: [WeeklyWeatherData] = [.init(weekDay: "오늘", + weatherImage: weatherImages[0], + chanceOfPrecipitation: nil, + minimumTemperature: "15°", + temperatureBarImage: "bar1", + maximumTemperature: "29°"), + .init(weekDay: "월", + weatherImage: weatherImages[1], + chanceOfPrecipitation: "60%", + minimumTemperature: "18°", + temperatureBarImage: "bar2", + maximumTemperature: "27°"), + .init(weekDay: "화", + weatherImage: weatherImages[1], + chanceOfPrecipitation: "60%", + minimumTemperature: "20°", + temperatureBarImage: "bar3", + maximumTemperature: "25°"), + .init(weekDay: "수", + weatherImage: weatherImages[1], + chanceOfPrecipitation: "70%", + minimumTemperature: "17°", + temperatureBarImage: "bar4", + maximumTemperature: "25°"), + .init(weekDay: "목", + weatherImage: weatherImages[1], + chanceOfPrecipitation: "50%", + minimumTemperature: "17°", + temperatureBarImage: "bar5", + maximumTemperature: "25°"), + .init(weekDay: "금", + weatherImage: weatherImages[2], + chanceOfPrecipitation: nil, + minimumTemperature: "20°", + temperatureBarImage: "bar6", + maximumTemperature: "26°"), + .init(weekDay: "토", + weatherImage: weatherImages[0], + chanceOfPrecipitation: nil, + minimumTemperature: "18°", + temperatureBarImage: "bar4", + maximumTemperature: "25°"), + .init(weekDay: "일", + weatherImage: weatherImages[1], + chanceOfPrecipitation: "50%", + minimumTemperature: "13°", + temperatureBarImage: "bar2", + maximumTemperature: "21°"), + .init(weekDay: "월", + weatherImage: weatherImages[1], + chanceOfPrecipitation: "50%", + minimumTemperature: "12°", + temperatureBarImage: "bar4", + maximumTemperature: "19°"), + .init(weekDay: "화", + weatherImage: weatherImages[0], + chanceOfPrecipitation: nil, + minimumTemperature: "18°", + temperatureBarImage: "bar3", + maximumTemperature: "25°"), + .init(weekDay: "수", + weatherImage: weatherImages[0], + chanceOfPrecipitation: nil, + minimumTemperature: "18°", + temperatureBarImage: "bar5", + maximumTemperature: "25°")] diff --git a/Weather-App/Weather-App/Resources/Extension/Extension+UIView.swift b/Weather-App/Weather-App/Resources/Extension/Extension+UIView.swift new file mode 100644 index 0000000..b1024d4 --- /dev/null +++ b/Weather-App/Weather-App/Resources/Extension/Extension+UIView.swift @@ -0,0 +1,17 @@ +// +// Extension+UIView.swift +// Weather-App +// +// Created by yeonsu on 11/8/23. +// + +import UIKit + +extension UIView { + func addSubviews(_ views: UIView...) { + views.forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + addSubview($0) + } + } +} diff --git a/Weather-App/Weather-App/Fonts/SF-Pro-Text-Bold.otf b/Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Bold.otf similarity index 100% rename from Weather-App/Weather-App/Fonts/SF-Pro-Text-Bold.otf rename to Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Bold.otf diff --git a/Weather-App/Weather-App/Fonts/SF-Pro-Text-Light.otf b/Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Light.otf similarity index 100% rename from Weather-App/Weather-App/Fonts/SF-Pro-Text-Light.otf rename to Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Light.otf diff --git a/Weather-App/Weather-App/Fonts/SF-Pro-Text-Medium.otf b/Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Medium.otf similarity index 100% rename from Weather-App/Weather-App/Fonts/SF-Pro-Text-Medium.otf rename to Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Medium.otf diff --git a/Weather-App/Weather-App/Fonts/SF-Pro-Text-Regular.otf b/Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Regular.otf similarity index 100% rename from Weather-App/Weather-App/Fonts/SF-Pro-Text-Regular.otf rename to Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Regular.otf diff --git a/Weather-App/Weather-App/Fonts/SF-Pro-Text-Thin.otf b/Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Thin.otf similarity index 100% rename from Weather-App/Weather-App/Fonts/SF-Pro-Text-Thin.otf rename to Weather-App/Weather-App/Resources/Fonts/SF-Pro-Text-Thin.otf diff --git a/Weather-App/Weather-App/SceneDelegate.swift b/Weather-App/Weather-App/SceneDelegate.swift index 9ba8877..852059a 100644 --- a/Weather-App/Weather-App/SceneDelegate.swift +++ b/Weather-App/Weather-App/SceneDelegate.swift @@ -18,10 +18,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let windowScene = (scene as? UIWindowScene) else { return } - let window = UIWindow(windowScene: windowScene) - window.rootViewController = ViewController() - window.makeKeyAndVisible() - self.window = window +// let window = UIWindow(windowScene: windowScene) +// window.rootViewController = ListViewController() +// window.makeKeyAndVisible() +// self.window = window + + window = UIWindow(windowScene: windowScene) + let mainViewController = UINavigationController(rootViewController: ListViewController()) + window?.rootViewController = mainViewController + window?.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) { diff --git a/Weather-App/Weather-App/Service/GetWeatherService.swift b/Weather-App/Weather-App/Service/GetWeatherService.swift new file mode 100644 index 0000000..911715e --- /dev/null +++ b/Weather-App/Weather-App/Service/GetWeatherService.swift @@ -0,0 +1,26 @@ +// +// GetWeatherService.swift +// Weathers +// +// Created by yeonsu on 11/14/23. +// + +import Foundation +import Combine + +class GetWeatherService { + func fetchWeather(city: String) -> AnyPublisher { + guard let url = URL(string: Constants.URLs.weather(city: city)) else { + fatalError("Invalid") + } + + return URLSession.shared.dataTaskPublisher(for: url) + .catch { error in + return Fail(error: error).eraseToAnyPublisher() + } + .map { $0.data } + .decode(type: WeatherResponse.self, decoder: JSONDecoder()) + .receive(on: RunLoop.main) + .eraseToAnyPublisher() + } +} diff --git a/Weather-App/Weather-App/Util/Constants.swift b/Weather-App/Weather-App/Util/Constants.swift new file mode 100644 index 0000000..b55435c --- /dev/null +++ b/Weather-App/Weather-App/Util/Constants.swift @@ -0,0 +1,17 @@ +// +// Constants.swift +// Weathers +// +// Created by yeonsu on 11/14/23. +// + +import Foundation + +struct Constants { + struct URLs { + static func weather(city: String) -> String { + return "https://api.openweathermap.org/data/2.5/weather?q=\(city)&appid=a4766e8295d121a9d02bf06520ee56b6&units=metric" + } + static let weather = "https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=a4766e8295d121a9d02bf06520ee56b6&units=metric" + } +} diff --git a/Weather-App/Weather-App/ViewController.swift b/Weather-App/Weather-App/ViewController.swift deleted file mode 100644 index 467525d..0000000 --- a/Weather-App/Weather-App/ViewController.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ViewController.swift -// Weather-App -// -// Created by yeonsu on 10/19/23. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - - self.view.backgroundColor = .red - } - - -} - diff --git a/Weather-App/Weather-App/Views/Detail/DetailViewController.swift b/Weather-App/Weather-App/Views/Detail/DetailViewController.swift new file mode 100644 index 0000000..51491ea --- /dev/null +++ b/Weather-App/Weather-App/Views/Detail/DetailViewController.swift @@ -0,0 +1,342 @@ +// +// DetailViewController.swift +// Weather-App +// +// Created by yeonsu on 11/8/23. +// + +import UIKit +import Then +import SnapKit + +class DetailViewController: UIViewController { + + let scrollView = UIScrollView() + var contentView = UIView() + + private let locationText = UILabel().then { + let customFont = UIFont(name: "SFProText-Medium", size: 36.0) + $0.font = customFont + $0.text = "의정부시" + $0.textColor = .white + } + + private let degreeNumber = UILabel().then { + let customDegreeFont = UIFont(name: "SFProText-Thin", size: 102.0) + $0.font = customDegreeFont + $0.text = "21°" + $0.textColor = .white + } + + private let statusText = UILabel().then { + let customStatusFont = UIFont(name: "SFProText-Regular", size: 24.0) + $0.font = customStatusFont + $0.text = "흐림" + $0.textColor = .white + } + + private let todayDegreeNumber = UILabel().then { + let customStatusFont = UIFont(name: "SFProText-Medium", size: 20.0) + $0.font = customStatusFont + $0.text = "최고:29° 최저:15°" + $0.textColor = .white + } + + private let descriptionView = UIView().then { + $0.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.01) + $0.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.05).cgColor + $0.layer.borderWidth = 1 + $0.layer.cornerRadius = 15 + } + + private var todayWeatherScriptLabel = UILabel().then { + $0.textColor = .white + $0.text = "08:00~09:00에 강우 상태가, 18:00에 한때 흐린 상태가 예상됩니다." + $0.font = UIFont(name: "SFProText-Regular", size: 18) + $0.numberOfLines = 0 + } + + private var lineView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let horizontalCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()).then { + $0.backgroundColor = .clear + } + + private let weeklyWeatherView = UIView().then { + $0.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.03) + $0.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.05).cgColor + $0.layer.borderWidth = 1 + $0.layer.cornerRadius = 15 + } + + private let calendarIcon = UIImageView().then { + $0.image = UIImage(systemName: "calendar") + $0.tintColor = UIColor(white: 1, alpha: 0.3) + } + + private let weeklyWeatherSubTitle = UILabel().then { + $0.font = UIFont(name: "SFProText-Regular", size: 15) + $0.text = "10일간의 일기예보" + $0.textColor = .white.withAlphaComponent(0.3) + } + + private let weeklyWeatherTableView = UITableView(frame: .zero, style: .plain).then { + $0.backgroundColor = .clear + $0.separatorStyle = .singleLine + $0.separatorColor = .white + $0.isScrollEnabled = false + } + + private let mapiconView = UIButton().then { + let imageView = UIImage(named: "icon-map") + $0.setImage(imageView, for: .normal) + } + + private let listIconView = UIButton().then { + let imageView = UIImage(named: "icon-list") + $0.setImage(imageView, for: .normal) + } + + private let bottomLineView = UIView().then { + $0.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.05) + } + + private let pageIconView = UIImageView().then { + $0.image = UIImage(named: "pageIcon") + } + + private let bottombgView = UIView().then { + $0.backgroundColor = .black.withAlphaComponent(0.9) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationController?.navigationBar.isHidden = true + + setBackgroundImage() + setConstraints() + + setCollectionViewConfig() + setCollectionViewLayout() + + setTableViewConfig() + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapListIcon)) + listIconView.addGestureRecognizer(tapGesture) + } + + func setBackgroundImage() { + let backgroundImage = UIImageView().then { + $0.image = UIImage(named: "bg") + $0.contentMode = .scaleAspectFill + } + + contentView.addSubview(backgroundImage) + + backgroundImage.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + func setConstraints() { + scrollView.showsVerticalScrollIndicator = false + scrollView.contentInsetAdjustmentBehavior = .never + scrollView.alwaysBounceVertical = true + + self.scrollView.backgroundColor = .black + + self.view.addSubviews(scrollView, + bottombgView, + mapiconView, + listIconView, + bottomLineView, + pageIconView) + + scrollView.addSubviews(contentView) + + contentView.addSubviews(locationText, + degreeNumber, + statusText, + todayDegreeNumber, + descriptionView, + weeklyWeatherView) + + descriptionView.addSubviews(todayWeatherScriptLabel, + lineView, + horizontalCollectionView) + + weeklyWeatherView.addSubviews(calendarIcon, + weeklyWeatherSubTitle, + weeklyWeatherTableView) + + contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true + let contentViewHeight = contentView.heightAnchor.constraint(greaterThanOrEqualTo: view.heightAnchor) + contentViewHeight.priority = .defaultLow + contentViewHeight.isActive = true + + scrollView.snp.makeConstraints { + $0.top.equalTo(self.view.snp.top) + $0.bottom.equalTo(self.view.snp.bottom) + $0.leading.trailing.equalToSuperview() + } + + contentView.snp.makeConstraints { + $0.top.equalTo(scrollView.contentLayoutGuide.snp.top) + $0.bottom.equalTo(scrollView.contentLayoutGuide.snp.bottom) + $0.leading.equalTo(scrollView.contentLayoutGuide.snp.leading) + $0.trailing.equalTo(scrollView.contentLayoutGuide.snp.trailing) + } + + locationText.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().inset(78) + } + + degreeNumber.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalTo(locationText.snp.bottom).offset(4) + } + + statusText.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalTo(degreeNumber.snp.bottom).offset(4) + } + + todayDegreeNumber.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalTo(statusText.snp.bottom).offset(4) + } + + descriptionView.snp.makeConstraints { + $0.top.equalTo(todayDegreeNumber.snp.bottom).offset(44) + $0.leading.equalToSuperview().inset(20) + $0.trailing.equalToSuperview().inset(20) + $0.height.equalTo(212) + } + todayWeatherScriptLabel.snp.makeConstraints { + $0.top.equalTo(descriptionView.snp.top).inset(10) + $0.leading.equalTo(descriptionView.snp.leading).inset(15) + $0.trailing.equalTo(descriptionView.snp.trailing).inset(15) + } + lineView.snp.makeConstraints { + $0.top.equalTo(todayWeatherScriptLabel.snp.bottom).offset(11) + $0.leading.equalTo(descriptionView.snp.leading).inset(14) + $0.trailing.equalTo(descriptionView.snp.trailing).inset(0) + $0.height.equalTo(0.2) + } + horizontalCollectionView.snp.makeConstraints { + $0.top.equalTo(lineView.snp.bottom) + $0.bottom.equalTo(descriptionView.snp.bottom) + $0.leading.equalTo(descriptionView.snp.leading).inset(20) + $0.trailing.equalTo(descriptionView.snp.trailing).inset(20) + } + weeklyWeatherView.snp.makeConstraints { + $0.top.equalTo(descriptionView.snp.bottom).offset(20) + $0.bottom.equalToSuperview().inset(86) + $0.height.equalTo(675) + $0.leading.equalToSuperview().inset(20) + $0.trailing.equalToSuperview().inset(20) + } + calendarIcon.snp.makeConstraints { + $0.top.equalToSuperview().inset(8) + $0.leading.equalToSuperview().inset(15) + } + weeklyWeatherSubTitle.snp.makeConstraints { + $0.top.equalTo(calendarIcon) + $0.leading.equalTo(calendarIcon.snp.trailing).offset(6) + } + weeklyWeatherTableView.snp.makeConstraints { + $0.top.equalTo(weeklyWeatherSubTitle.snp.bottom).offset(6) + $0.leading.trailing.bottom.equalToSuperview() + } + mapiconView.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(34) + $0.leading.equalToSuperview().inset(10) + } + listIconView.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(34) + $0.trailing.equalToSuperview().inset(10) + } + pageIconView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalTo(listIconView) + $0.width.equalTo(52) + } + bottomLineView.snp.makeConstraints { + $0.bottom.equalTo(bottombgView.snp.top) + $0.leading.equalToSuperview() + $0.trailing.equalToSuperview() + $0.height.equalTo(1) + } + bottombgView.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.trailing.equalToSuperview() + $0.height.equalTo(82) + $0.bottom.equalTo(0) + } + } + + private func setCollectionViewConfig() { + self.horizontalCollectionView.register(TodayWeatherCollectionViewCell.self, + forCellWithReuseIdentifier: TodayWeatherCollectionViewCell.identifier) + self.horizontalCollectionView.delegate = self + self.horizontalCollectionView.dataSource = self + } + + private func setCollectionViewLayout() { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.itemSize = CGSize(width: 44 , height: 122) + flowLayout.scrollDirection = .horizontal + flowLayout.minimumLineSpacing = 22 + flowLayout.minimumInteritemSpacing = 3 + self.horizontalCollectionView.setCollectionViewLayout(flowLayout, animated: false) + } + + private func setTableViewConfig() { + self.weeklyWeatherTableView.register(WeeklyWeatherTableViewCell.self, + forCellReuseIdentifier: WeeklyWeatherTableViewCell.identifier) + self.weeklyWeatherTableView.delegate = self + self.weeklyWeatherTableView.dataSource = self + } + + @objc func tapListIcon() { + let listVC = ListViewController() + self.navigationController?.popViewController(animated: true) + } +} + +extension DetailViewController: UICollectionViewDelegate {} + +extension DetailViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return todayWeatherData.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let item = collectionView.dequeueReusableCell(withReuseIdentifier: TodayWeatherCollectionViewCell.identifier, + for: indexPath) as? TodayWeatherCollectionViewCell else {return UICollectionViewCell()} + item.bindData(data: todayWeatherData[indexPath.row]) + return item + } +} + +extension DetailViewController: UITableViewDelegate {} + +extension DetailViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return weeklyWeatherData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: WeeklyWeatherTableViewCell.identifier, + for: indexPath) as? WeeklyWeatherTableViewCell else {return UITableViewCell()} + + cell.bindData(data: weeklyWeatherData[indexPath.row]) + cell.backgroundColor = .clear + cell.selectionStyle = .none + return cell + } + +} diff --git a/Weather-App/Weather-App/Views/Detail/TodayWeatherCollectionViewCell.swift b/Weather-App/Weather-App/Views/Detail/TodayWeatherCollectionViewCell.swift new file mode 100644 index 0000000..61d130c --- /dev/null +++ b/Weather-App/Weather-App/Views/Detail/TodayWeatherCollectionViewCell.swift @@ -0,0 +1,67 @@ +// +// TodayWeatherCollectionViewCell.swift +// Weather-App +// +// Created by yeonsu on 11/8/23. +// + +import UIKit +import Then +import SnapKit + +class TodayWeatherCollectionViewCell: UICollectionViewCell { + + static let identifier: String = "TodayWeatherCollectionViewCell" + + private let timeLabel = UILabel().then { + $0.font = UIFont(name: "SFProText-Medium", size: 17.0) + $0.textColor = .white + } + + private let weatherIcon = UIImageView().then { + $0.contentMode = .scaleAspectFit + } + + private let degree = UILabel().then { + $0.font = UIFont(name: "SFProText-Medium", size: 22.0) + $0.textColor = .white + } + + override init(frame: CGRect) { + super.init(frame: frame) + setConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setConstraints() { + self.addSubviews(timeLabel, weatherIcon, degree) + + timeLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(4) + $0.centerX.equalToSuperview() + } + + weatherIcon.snp.makeConstraints { + $0.top.equalTo(timeLabel.snp.bottom).offset(14) + $0.centerX.equalTo(timeLabel) + } + + degree.snp.makeConstraints { + $0.bottom.equalToSuperview().offset(4) + $0.centerX.equalTo(timeLabel) + } + + } + + var itemRow: Int? + + func bindData(data: TodayWeatherData) { + self.timeLabel.text = data.time + self.weatherIcon.image = UIImage(named: data.weatherImage) + self.degree.text = data.hourlyWeather + } + +} diff --git a/Weather-App/Weather-App/Views/Detail/WeeklyWeatherTableViewCell.swift b/Weather-App/Weather-App/Views/Detail/WeeklyWeatherTableViewCell.swift new file mode 100644 index 0000000..bfc4d22 --- /dev/null +++ b/Weather-App/Weather-App/Views/Detail/WeeklyWeatherTableViewCell.swift @@ -0,0 +1,93 @@ +// +// WeeklyWeatherTableViewCell.swift +// Weather-App +// +// Created by yeonsu on 11/9/23. +// + +import UIKit +import Then +import SnapKit + +class WeeklyWeatherTableViewCell: UITableViewCell { + + static let identifier: String = "WeeklyWeatherTableViewCell" + + private let weekLabel = UILabel().then { + $0.font = UIFont(name: "SFProText-Medium", size: 22) + $0.textColor = .white + } + + private let weatherIcon = UIImageView().then { + $0.contentMode = .scaleAspectFill + } + + private let minTemperatureLabel = UILabel().then { + $0.font = UIFont(name: "SFProText-Medium", size: 22) + $0.textColor = .white + } + + private let temperatureBarImage = UIImageView().then { + $0.contentMode = .scaleAspectFill + } + + private let maxTemperatureLabel = UILabel().then { + $0.font = UIFont(name: "SFProText-Medium", size: 22) + $0.textColor = .white + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setConstraints() { + self.contentView.addSubviews(weekLabel, + weatherIcon, + minTemperatureLabel, + temperatureBarImage, + maxTemperatureLabel) + + weekLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.top.equalToSuperview().inset(14) + $0.leading.equalToSuperview().inset(16) + } + weatherIcon.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.top.equalToSuperview().inset(14) + $0.leading.equalTo(weekLabel.snp.trailing).offset(30) + $0.width.equalTo(32) + $0.height.equalTo(34) + } + minTemperatureLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.top.equalToSuperview().inset(14) + $0.leading.equalTo(weatherIcon.snp.trailing).offset(15) + } + temperatureBarImage.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(minTemperatureLabel.snp.trailing).offset(6) + $0.trailing.equalTo(maxTemperatureLabel.snp.leading).offset(-6) + $0.width.equalTo(108) + } + maxTemperatureLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.top.equalToSuperview().inset(14) + $0.leading.equalTo(temperatureBarImage.snp.trailing).inset(10) + $0.trailing.equalToSuperview().inset(17) + } + } + + func bindData(data: WeeklyWeatherData) { + self.weekLabel.text = data.weekDay + self.weatherIcon.image = UIImage(named: data.weatherImage) + self.minTemperatureLabel.text = data.minimumTemperature + self.temperatureBarImage.image = UIImage(named: data.temperatureBarImage) + self.maxTemperatureLabel.text = data.maximumTemperature + } +} diff --git a/Weather-App/Weather-App/Views/List/ListTableViewCell.swift b/Weather-App/Weather-App/Views/List/ListTableViewCell.swift new file mode 100644 index 0000000..ce50d0d --- /dev/null +++ b/Weather-App/Weather-App/Views/List/ListTableViewCell.swift @@ -0,0 +1,138 @@ +// +// ListTableViewCell.swift +// Weather-App +// +// Created by yeonsu on 11/8/23. +// + +import UIKit +import Then +import SnapKit +import Combine + +class ListTableViewCell: UITableViewCell { + + static let identifier: String = "ItemListTableViewCell" + + private var getWeatherService: GetWeatherService = GetWeatherService() + private var cancellable: AnyCancellable? + public var cancellables: Set = [] + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + // setPublishers() + setConstraints() + + } + + override func prepareForReuse() { + cancellable?.cancel() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private let listView = UIImageView().then { + $0.image = UIImage(named: "bg-list") + } + + private let myLocationLabel = UILabel().then { + let customTitleFont = UIFont(name: "SFProText-Bold", size: 24.0) + $0.font = customTitleFont + $0.text = "" + $0.textColor = .white + } + + private let currentTimeLabel = UILabel().then { + let customTitleFont = UIFont(name: "SFProText-Medium", size: 17.0) + $0.font = customTitleFont + $0.text = "" + $0.textColor = .white + } + + private let degreeNumber = UILabel().then { + let customDegreeFont = UIFont(name: "SFProText-Light", size: 52.0) + $0.font = customDegreeFont + $0.text = "" + $0.textColor = .white + } + + private let statusText = UILabel().then { + let customStatusFont = UIFont(name: "SFProText-Regular", size: 16.0) + $0.font = customStatusFont + $0.text = "" + $0.textColor = .white + } + + private let todayDegreeNumber = UILabel().then { + let customTodayDegreeFont = UIFont(name: "SFProText-Regular", size: 16.0) + $0.font = customTodayDegreeFont + $0.text = "" + $0.textColor = .white + } + + private func setConstraints() { + + self.contentView.addSubview(listView) + self.listView.addSubviews(myLocationLabel, + currentTimeLabel, + degreeNumber, + statusText, + todayDegreeNumber + ) + self.contentView.backgroundColor = .black + + listView.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.trailing.equalToSuperview() + $0.height.equalTo(117) + } + myLocationLabel.snp.makeConstraints { + $0.leading.equalToSuperview().inset(16) + $0.top.equalToSuperview().inset(10) + } + currentTimeLabel.snp.makeConstraints { + $0.leading.equalToSuperview().inset(16) + $0.top.equalTo(myLocationLabel.snp.bottom).offset(2) + } + degreeNumber.snp.makeConstraints { + $0.top.equalToSuperview() + $0.trailing.equalToSuperview().inset(16) + } + statusText.snp.makeConstraints { + $0.leading.equalTo(currentTimeLabel) + $0.bottom.equalToSuperview().inset(10) + } + todayDegreeNumber.snp.makeConstraints { + $0.trailing.equalTo(degreeNumber) + $0.bottom.equalTo(statusText) + } + } + + func setUI(weather: WeatherResponse) { + myLocationLabel.text = weather.name + currentTimeLabel.text = convertTimeZoneOffsetToString(weather.timezone) + degreeNumber.text = "\(formatNumberToOneDecimalPlace(weather.main.temp!))°" + todayDegreeNumber.text = "최고:\(formatNumberToOneDecimalPlace(weather.main.temp_max!))° 최저:\(formatNumberToOneDecimalPlace(weather.main.temp_min!))°" + } + + func convertTimeZoneOffsetToString(_ offset: Int) -> String { + let hours = offset / 3600 + let minutes = abs((offset % 3600) / 60) + + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm" + formatter.timeZone = TimeZone(secondsFromGMT: offset) + + let formattedString = formatter.string(from: Date()) + + return formattedString + } + + func formatNumberToOneDecimalPlace(_ number: Double) -> String { + let formattedNumber = String(format: "%.1f", number) + return formattedNumber + } +} diff --git a/Weather-App/Weather-App/Views/List/ListViewController.swift b/Weather-App/Weather-App/Views/List/ListViewController.swift new file mode 100644 index 0000000..e845a17 --- /dev/null +++ b/Weather-App/Weather-App/Views/List/ListViewController.swift @@ -0,0 +1,159 @@ +// +// ListViewController.swift +// Weather-App +// +// Created by yeonsu on 11/8/23. +// + +import UIKit +import Then +import SnapKit +import Combine + +class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + private var getWeatherService: GetWeatherService = GetWeatherService() + private var cancellable: AnyCancellable? + + private let cities: [String] = ["seoul", "gwangju", "gumi", "gunsan", "daegu"] + private var weatherResponse: [WeatherResponse] = [] + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return weatherResponse.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: ListTableViewCell.identifier, for: indexPath) as? ListTableViewCell else { + return UITableViewCell() + } + + var city = cities[indexPath.row] + cell.setUI(weather: weatherResponse[indexPath.row]) + + return cell + } + + private let etcButton = UIButton().then { + let etcIcon = UIImage(named: "icon-etc") + $0.setImage(etcIcon, for: .normal) + } + + private let tableView = UITableView(frame: .zero, style: .plain).then { + $0.backgroundColor = .init(.black) + $0.separatorStyle = .none + } + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationController?.navigationBar.isHidden = true + + self.view.addSubviews(etcButton, + tableView) + + setPublishers() + setConstraints() + setTableViewConfig() + self.tableView.alwaysBounceVertical = true + + } + + private func setTableViewConfig() { + self.tableView.register(ListTableViewCell.self, + forCellReuseIdentifier: ListTableViewCell.identifier) + self.tableView.delegate = self + self.tableView.dataSource = self + } + + private func setConstraints() { + etcButton.snp.makeConstraints { + $0.top.equalTo(self.view.safeAreaLayoutGuide.snp.top) + $0.trailing.equalToSuperview().inset(20) + } + + tableView.snp.makeConstraints { + $0.top.equalTo(etcButton.snp.bottom) + $0.leading.equalToSuperview().inset(20) + $0.trailing.equalToSuperview().inset(20) + $0.bottom.equalToSuperview() + } + } + + public let searchTextField = UITextField().then { + let placeholderTextColor = UIColor.lightGray + $0.borderStyle = .roundedRect + $0.placeholder = "도시 또는 공항 검색" + $0.textColor = .white + + let attributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.foregroundColor: placeholderTextColor + ] + $0.attributedPlaceholder = NSAttributedString(string: $0.placeholder ?? "", attributes: attributes) + $0.backgroundColor = .secondarySystemFill + + let imageView = UIImageView() + imageView.frame = CGRect(x: 0, y: 0, width: 24, height: 24) + imageView.image = UIImage(named: "icon-search") + $0.leftView = imageView + $0.leftViewMode = .always + + let marginView = UIView() + imageView.frame = CGRect(x: 0, y: 0, width: 8, height: 24) + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let headerView = UIView() + + let appTitle = UILabel().then { + let customFont = UIFont(name: "SFProText-Bold", size: 36.0) + $0.font = customFont + $0.text = "날씨" + $0.textColor = .white + } + + headerView.addSubviews(appTitle, searchTextField) + appTitle.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.equalToSuperview() + } + searchTextField.snp.makeConstraints { + $0.top.equalTo(appTitle.snp.bottom).offset(8) + $0.leading.equalToSuperview() + $0.trailing.equalToSuperview() + $0.size.equalTo(44) + } + return headerView + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 108.0 + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 132.0 + } + + + @objc func tapMyLocationView() { + let detailVC = DetailViewController() + self.navigationController?.pushViewController(detailVC, animated: true) + } + + private func setPublishers() { + let publisher = Publishers.Sequence<[String], Never>(sequence: cities) + + self.cancellable = publisher + .flatMap { [weak self] city in + guard let self = self else {return Just(WeatherResponse(main: Main.placeholder, timezone: 0, name: "")).eraseToAnyPublisher() } // weak self로 만들어주는 publisher + return self.getWeatherService.fetchWeather(city: city) + .catch { error in + Just(WeatherResponse(main: Main.placeholder, timezone: 0, name: "")) + } + .eraseToAnyPublisher() + } + .collect() // 5개 데이터 -> 배열로! + .sink { [weak self] weather in + self?.weatherResponse = weather + self?.tableView.reloadData() + } + } +}