β
οΈ
weatherApp-iOS-practice
π
κΈ°λ₯ μμΈ
-
λμ μ΄λ¦μ μ λ ₯νλ©΄ νμ¬ λ μ¨ μ 보λ₯Ό κ°μ Έμ νλ©΄μ νμλκ² λ§λ€μ΄μΌ ν©λλ€
-
λμ μ΄λ¦μ μλͺ» μ λ ₯νλ©΄ μλ²λ‘λΆν° μλ΅λ°μ μλ¬ λ©μμ§κ° alertμΌλ‘ νμ λ©λλ€
π
Check Point !
π·
Current Weather API (OpenWeather API)
json κ³Ό struct ꡬ쑰체(model) mapping νκΈ°
// WeatherInfo.swift
import Foundation
// Codable μ μμ μ λ³ννκ±°λ, μΈλΆννμΌλ‘ λ³ν ν μ μλ (μ, .json) νμ
μ μλ―Έν¨
// Codable μ decodable(μμ μ μΈλΆμ decoding νμ
), encodable(μμ μ μΈλΆμμ encoding νμ
)
// Codable protocol μ μ±ν νλ€λ κ²μ Json decoding, encoding μ΄ λͺ¨λ κ°λ₯ νλ€λ κ²μ, μ¦ Json <-> WeatherInfo κ°μ²΄
struct WeatherInfo: Codable {
let weather: [Weather]
let temp: Temp
let nameL: String
enum CodingKeys: String, CodingKey {
case weather
case temp = "main"
case name
}
}
struct Weather: Codable {
let id: Int
let main: String
let description: String
let icon: String
}
// λ§μ½ jsonμ property μ΄λ¦κ³Ό type μ μ΄λ¦μ΄ λ€λ₯Ό κ²½μ° type λ΄λΆμμ codingKeys λΌλ String type μ μ΄κ±°νμ μ μΈνκ³ codingKey protocol μ μ€μνκ² λ§λ€μ΄μΌ ν¨
// main property μ json μμ temp struct μ mapping μν€κΈ° μν΄μ property μ μν¨
struct Temp: Codable {
let temp: Double
let feelsLike: Double
let minTemp: Double
let maxTemp: Double
enum CodingKeys: String, CodingKey {
case temp
case feelsLike = "feels_like"
case minTemp = "temp_min"
case maxTemp = "temp_max"
}
}
response ν dataλ₯Ό UI μ μ λ°μ΄νΈ
//
// UIμ°½μ weatherInfo κ° λνλκ² νλ method
func configureView(weatherInfo: WeatherInfo) {
self.cityNameLabel.text = weatherInfo.name
// weatherInfor μμ wather μ 첫λ²μ§Έ μμμ λμ
if let weather = weatherInfo.weather.first {
self.weatherDescriptionLabel.text = weather.description
}
self.tempLabel.text = "\(Int(weatherInfo.temp.temp))Β°C"
self.minTempLabel.text = "μ΅μ : \(Int(weatherInfo.temp.minTemp))Β°C"
self.maxTempLabel.text = "μ΅κ³ : \(Int(weatherInfo.temp.maxTemp))Β°C"
}
- λμμ νμ¬ λ μ¨ μ 보λ₯Ό κ°μ Έμ΅λλ€
π·
URLSession
func getCurrentWeather(cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=metric&lang=kr&appid=0fb8463dce1de96897cba0b1eff08e18") else { return }
// session μ default session μΌλ‘ μ€μ
let session = URLSession(configuration: .default)
// compression handler λ‘μ¨ closure λ§€κ° λ³μμ data(μλ²μμ μλ΅ λ°μ data), response(HTTP header λ μν μ½λμ metaData), error(error μ½λ λ°ν)
session.dataTask(with: url) { [weak self] data, response, error in
......
}
Describing check point in details in Jacob's DevLog - https://jacobko.info/ios/ios-06/
β
Error Check Point
πΆ
API Response Error λ°μμ Error μ²λ¦¬
μμ κ°μ΄ textField μμ λμμ΄λ¦μ΄ μ€νλ κ²μμ΄ λμ§ μμΌλ©΄, 404 error κ° λ°μν©λλ€. κ·Έλ΄λ alert μ°½μΌλ‘ λμμ΄λ¦μ΄ μΌμΉνμ§ μμ΅λλ€ λΌλ λμ€κ² νλ code λ λ€μκ³Ό κ°μ΅λλ€
- Error message μ²λ¦¬λ₯Ό μν struct λͺ¨λΈ μμ±
// in ViewController.swift
// Error message κ° alert μ νμλκ² νλ logic
func showAlert(message: String) {
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "νμΈ", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// URLSession μ μ΄μ©ν΄μ currentWeather APIλ₯Ό νΈμΆνκΈ°
func getCurrentWeather(cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=metric&lang=kr&appid=0fb8463dce1de96897cba0b1eff08e18") else { return }
// session μ default session μΌλ‘ μ€μ
let session = URLSession(configuration: .default)
// compression handler λ‘μ¨ closure λ§€κ° λ³μμ data(μλ²μμ μλ΅ λ°μ data), response(HTTP header λ μν μ½λμ metaData), error(error μ½λ λ°ν)
session.dataTask(with: url) { [weak self] data, response, error in
// μλ΅λ°μ response (json data)λ₯Ό weatherInfo struct μ decoding λκ² νλ logic
let successRange = (200..<300)
guard let data = data, error == nil else { return }
let decorder = JSONDecoder()
// μλ΅λ°μ data μ statusCode κ° 200λ²λ (200 ~ 299) μΌλ
if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
guard let weatherInfo = try? decorder.decode(WeatherInfo.self, from: data) else { return }
// debugPrint(weatherInfo)
// λ°μμ¨ λ°μ΄ν°λ₯Ό UI μ νμνκΈ° μν΄μλ main thread μμ μμ
μ μ§ν νμ¬ λ©
DispatchQueue.main.async {
self?.weatherStackView.isHidden = false
self?.configureView(weatherInfo: weatherInfo)
}
} else { // status code κ° 200 λ²λκ° μλλ©΄ error μν μ΄λκΉ error message μμ± logic
guard let errorMessage = try? decorder.decode(ErrorMessage.self, from: data) else { return }
// debugPrint(errorMessage)
// main thread μμ alert μ΄ νμλκ² ν΄μΌλ¨
DispatchQueue.main.async {
self?.showAlert(message: errorMessage.message)
}
}
}.resume() // app μ΄ μ€νλκ² ν¨
}
π
Reference
Jacob's DevLog - https://jacobko.info/ios/ios-08/
μμ§μ μ΄λ ΅μ§ - https://greatpapa.tistory.com/66
fastcampus - https://fastcampus.co.kr/dev_online_iosappfinal