일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- CoreLocation
- SwiftUI Boolean 값
- swift
- CLLocationManagerDelegate
- RunningTimer
- 러닝타이머
- 클로저의 캡슐화
- Timer
- 한국어 개인정보처리방침
- WeatherManager
- Protocol
- MKMapItem
- weatherKit
- weak var
- UICollectionViewFlowLayout
- App Store Connect
- addannotation
- UIAlertAction
- font book
- 서체관리자
- 러닝기록앱
- Startign Assignments
- dispatchsource
- Required Reason API
- AnyObject
- MKMapViewDelegate
- Xcode
- 단일 책임원칙
- xcode로 날씨앱 만들기
- 영문 개인정보처리방침
Archives
- Today
- Total
VesselWheel
정해진 지역의 날씨를 호출하여 날씨 정보 표시하기(feat. OpenWeatherMap API) 본문
MVC 패턴으로 만든 OpenWeatherMap API의 특정지역(서울)의 날씨정보 호출 및 출력
1. OpenWeatherMap에서 가져온 데이터로 구조체 만들기
//
// WeatherModel.swift
// Weather777
//
// Created by Jason Yang on 2/5/24.
//
import Foundation
// MARK: - WeatherData
struct WeatherData: Codable {
let coord: Coord
let weather: [Weather]
let base: String
let main: Main
let visibility: Int
let wind: Wind
let clouds: Clouds
let dt: Int
let sys: Sys
let timezone, id: Int
let name: String
let cod: Int
}
// MARK: - Clouds
struct Clouds: Codable {
let all: Int
}
// MARK: - Coord
struct Coord: Codable {
let lon, lat: Double
}
// MARK: - Main
struct Main: Codable {
let temp, feelsLike, tempMin, tempMax: Double
let pressure, humidity: Int
enum CodingKeys: String, CodingKey {
case temp
case feelsLike = "feels_like"
case tempMin = "temp_min"
case tempMax = "temp_max"
case pressure, humidity
}
}
// MARK: - Sys
struct Sys: Codable {
let type, id: Int
let country: String
let sunrise, sunset: Int
}
// MARK: - Weather
struct Weather: Codable {
let id: Int
let main, description, icon: String
}
// MARK: - Wind
struct Wind: Codable {
let speed: Double
let deg: Int
}
2. 데이터 모델인 WeatherData를 활용하여 WeatherManager 클래스로 API 호출하기
//
// WeatherManager.swift
// Weather777
//
// Created by Jason Yang on 2/5/24.
//
import Foundation
// 에러 정의
enum NetworkError: Error {
case badUrl
case noData
case decodingError
}
class WeatherManager {
static let shared = WeatherManager()
private init() {}
// MARK: - Properties
//plist를 통해 숨긴 API 호출을 위한 프로퍼티
private var apiKey: String {
get {
// 생성한 .plist 파일 경로 불러오기
guard let filePath = Bundle.main.path(forResource: "WeatherKey", ofType: "plist") else {
fatalError("Couldn't find file 'WeatherKey.plist'.")
}
// .plist를 딕셔너리로 받아오기
let plist = NSDictionary(contentsOfFile: filePath)
// 딕셔너리에서 값 찾기
guard let value = plist?.object(forKey: "OPENWEATHERMAP_KEY") as? String else {
fatalError("Couldn't find key 'OPENWEATHERMAP_KEY' in 'KeyList.plist'.")
}
return value
}
}
//API 호출을 위한 매소드 // 서울지역이 아닌 곳을 하려면 어떻게 해야할까?
// 위경도 기준 : URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=\(apiKey)")
// 파라미터 추가 latitude: Double, longitude: Double,
// 서울지역 : URL(string: "https://api.openweathermap.org/data/2.5/weather?q=seoul&appid=\(apiKey)")
func getWeather(completion: @escaping(Result<WeatherData, NetworkError>) -> Void) {
let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=seoul&appid=\(apiKey)")
guard let url = url else {
return completion(.failure(.badUrl))
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
return completion(.failure(.noData))
}
// Data 타입으로 받은 리턴을 디코드
let weatherData = try? JSONDecoder().decode(WeatherData.self, from: data)
// 성공 시 성공한 데이터 저장
if let weatherData = weatherData {
completion(.success(weatherData))
} else {
completion(.failure(.decodingError))
}
}.resume() // dataTask 시작
print("Success")
}
}
3. ViewController에서 WeatherManager 클래스를 싱글톤 패턴 방식으로 데이터를 호출하여 비동기로 UI 출력하기
//
// ViewController.swift
// Weather777
//
// Created by Jason Yang on 2/5/24.
//
import UIKit
import SwiftUI
class ViewController: UIViewController {
var weather: Weather?
var main: Main?
// MARK: - UI Properties
let testLabel: UILabel = {
let label = UILabel()
label.text = "7팀 화이팅입니다.😃"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var weatherImage: UIImageView!
var tempLabel: UILabel!
var maxTempLabel: UILabel!
var minTempLabel: UILabel!
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setUI()
setAddSubView()
setLayout()
// data fetch, url에서 싱글톤패턴으로 받아온 데이터를 비동기적으로 호출하고, UI 업데이트를 위해 메인스레드에서 수행, 클로져 내부에서 강한참조 방지 weak self
WeatherManager.shared.getWeather { [weak self] result in
switch result {
case .success(let weatherResponse):
DispatchQueue.main.async {
self?.weather = weatherResponse.weather.first
self?.main = weatherResponse.main
self?.setWeatherUI()
}
case .failure(_ ):
print("error")
}
}
}
}
extension ViewController {
func setUI() {
// 배경색 지정
view.backgroundColor = .white
weatherImage = UIImageView()
weatherImage.translatesAutoresizingMaskIntoConstraints = false
tempLabel = UILabel()
tempLabel.translatesAutoresizingMaskIntoConstraints = false
maxTempLabel = UILabel()
maxTempLabel.translatesAutoresizingMaskIntoConstraints = false
minTempLabel = UILabel()
minTempLabel.translatesAutoresizingMaskIntoConstraints = false
}
func setAddSubView() {
view.addSubview(testLabel)
view.addSubview(weatherImage)
view.addSubview(tempLabel)
view.addSubview(maxTempLabel)
view.addSubview(minTempLabel)
}
func setLayout() {
NSLayoutConstraint.activate([
testLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
testLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// weatherImage의 제약 조건 설정
weatherImage.centerXAnchor.constraint(equalTo: view.centerXAnchor),
weatherImage.topAnchor.constraint(equalTo: testLabel.bottomAnchor, constant: 100),
weatherImage.widthAnchor.constraint(equalToConstant: 100),
weatherImage.heightAnchor.constraint(equalToConstant: 100),
// tempLabel의 제약 조건 설정
tempLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
tempLabel.topAnchor.constraint(equalTo: weatherImage.bottomAnchor, constant: 20),
// maxTempLabel의 제약 조건 설정
maxTempLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
maxTempLabel.topAnchor.constraint(equalTo: tempLabel.bottomAnchor, constant: 20),
// minTempLabel의 제약 조건 설정
minTempLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
minTempLabel.topAnchor.constraint(equalTo: maxTempLabel.bottomAnchor, constant: 20)
])
}
private func setWeatherUI() {
guard let icon = self.weather?.icon else {
return
}
let urlString = "https://openweathermap.org/img/wn/\(icon)@2x.png"
guard let url = URL(string: urlString) else {
return
}
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, response, error) in
guard let data = data, error == nil else {
return
}
DispatchQueue.main.async {
self.weatherImage.image = UIImage(data: data)
}
}
task.resume()
if let main = self.main {
let tempCelsius = main.temp
let tempMaxCelsius = main.tempMax
let tempMinCelsius = main.tempMin
DispatchQueue.main.async {
if let celsiustempLabel = self.convertFahrenheitToCelsius(tempCelsius),
let celsiusmaxTempLabel = self.convertFahrenheitToCelsius(tempMaxCelsius),
let celsiusminTempLabel = self.convertFahrenheitToCelsius(tempMinCelsius) {
// Call to method 'convertFahrenheitToCelsius' in closure requires explicit use of 'self' to make capture semantics explicit
self.tempLabel.text = "\(celsiustempLabel)"
self.maxTempLabel.text = "\(celsiusmaxTempLabel)"
self.minTempLabel.text = "\(celsiusminTempLabel)"
}
}
}
}
}
extension ViewController {
func convertFahrenheitToCelsius(_ fahrenheit: Double) -> String? {
let celsiusUnit = UnitTemperature.celsius
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 1
let celsius = celsiusUnit.converter.value(fromBaseUnitValue: fahrenheit)
if let formattedCelsius = formatter.string(from: celsius as NSNumber) {
return "\(formattedCelsius)°C"
}
return ""
}
}
// MARK: - Preview
struct PreView: PreviewProvider {
static var previews: some View {
ViewController().toPreview()
}
}
#if DEBUG
extension UIViewController {
private struct Preview: UIViewControllerRepresentable {
let viewController: UIViewController
func makeUIViewController(context: Context) -> UIViewController {
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
func toPreview() -> some View {
Preview(viewController: self)
}
}
#endif
OpenWeatherMap API에서 날씨 아이콘 호출 방법
https://openweathermap.org/weather-conditions
'Xcode Study' 카테고리의 다른 글
UIkit 기반으로 코딩할 때 SwifitUI의 preview 사용하기 (0) | 2024.02.07 |
---|---|
위치 정보와 날씨 정보 데이터를 받아 UI에 표현하기(feat. CoreLocation, OpenWeatherMap API) (0) | 2024.02.07 |
How to add SwiftUI in UIkit project programmatically (0) | 2024.02.04 |
MVVM 아키텍처 활용하기 (0) | 2024.02.03 |
뷰 계층에서 lazy 키워드 사용하기 (0) | 2024.02.02 |