본문 바로가기

Development/App (SwiftUI)

SwiftUI로 API에서 JSON 데이터 불러오는 방법: 아이폰 날씨 앱 개발기

1. Open API 날씨 어플리케이션 틀 만들기

Open API를 활용하여 날씨 어플리케이션의 틀을 만들어보고자 알아보았다.

기존 UIKit 방식에 비해 SwiftUI는 자료가 그리 많지는 않았다. 

 

먼저 날씨 정보를 제공해주는 API를 찾아야 하는데, Dark Sky API는 새로운 사용자를 받지 않고 곧 서비스 종료될 것이라 하기에 대안을 찾았다.

1-1. Current Weather Data API 개요

결론적으로 OpenWeatherMap에서 제공해주는 Current Weather Data API를 이용하기로 하였다.

openweathermap.org/api

 

Weather API - OpenWeatherMap

Please sign up and use our fast and easy-to-work weather APIs for free. Look at our monthly subscriptions for more options rather than the Free account that we provide you. Read How to start first and enjoy using our powerful weather APIs.

openweathermap.org

 

1-2. API Key 발급

"Current Weather Data" 아래의 API doc에 들어가보면 API에 대한 상세 정보가 적시되어 있다.

도시 이름 / 위·경도 등에 따라 API call을 할 수 있는데 나는 도시 이름을 기준으로 하기로 했다.

 

"https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=발급받은_API_key"에 접속해보면 JSON 형식의 데이터를 제공하는 것을 알 수 있다.

여기서 [발급받은_API_key] 부분에는 직접 발급받은 API 키를 입력해야 하는데, 회원가입 후 API key 메뉴에 들어가면 Default로 생성된 API 키가 있을 것이다. 

웹 브라우저 주소창에 주소를 입력하면 처음엔 에러 메시지가 나타날 수 있다. API 키가 바로 인증되지 않아서인것 같은데, 5분 정도 기다리면 제대로 작동할 것이다.

2. SwiftUI Data Parsing

그럼 이제 SwiftUI에서 데이터 파싱하는 방법을 알아보겠다. 

2-1. ContentView.swift

Xcode의 Single View App 프로젝트 (SwiftUI) -> ContentView.swift 에 다음과 같이 입력한다. 

 

import SwiftUI
import Combine
 
 struct Weather: Decodable {
     var main: String
     var description: String
 }
 
struct WeatherResponse: Decodable {
    let weather: [Weather]
}
 
struct ContentView: View {
     var body: some View {
        Button(action: loadData) {
            Text("Button")
        }

     }
 }

func loadData() {
    
    guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=발급받은_API_key") else {
        fatalError("Invalid URL")
    }
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data, error == nil else {
            return
        }
        
        let result = try? JSONDecoder().decode(WeatherResponse.self, from: data)
        if let result = result {
            print(result)
            result.weather.forEach {
                print($0.main)
                print($0.description)
            }
        }
        
        
    }.resume()
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

(위 코드의 상세 해석은 추후 다룰 예정이다.)

 

그리고 Cmd + R을 통해 실행시켜본다. ContentView Struct에 의해 생긴 Button을 누르면 Xcode의 콘솔에 정보가 print된다.

 

첫번째 print문 (result)에 의해 WeatherResponse(weather: [A.Weather(main: "Clouds", description: "overcast clouds")]), 

두번째 print문 ($0.main)에 의해 Clouds, 

세번째 print문 ($0.description)에 의해 overcast clouds가 출력되는 것을 볼 수 있다.

 

OpenWeatherMap API의 JSON 데이터는 이 외에도 상당히 많은 정보(최고·최저 기온, 습도, 구름양, 일출·일몰 시각 등)를 제공하기에, 체계적으로 정리할 필요가 생긴다.

 

따라서 다음과 같이

2-2. Weather.swift

//
//  Weather.swift
//  weather
//
//  Created by David Rozmajzl on 7/22/20.
//  Copyright © 2020 David Rozmajzl. All rights reserved.
//

import Foundation

struct Weather: Codable, Identifiable {
    var name: String?
    var id: Int?
    var coord: Coordinates?
    var weather: [Conditions]?
    var main: Main?
    var visibility: Int?
    var wind: Wind?
    var rain: Rain?
    var snow: Snow?
    var clouds: Cloud?
    var dt: Int?
    var sys: Sys?
    var timezone: Int?
}

struct Coordinates: Codable {
    var lon: Float?
    var lat: Float?
}

struct Conditions: Codable, Identifiable {
    var id: Int?
    var main: String?
    var description: String?
    var icon: String?
}

struct Main: Codable {
    var temp: Float?
    var feels_like: Float?
    var temp_min: Float?
    var temp_max: Float?
    var pressure: Float?
    var humidity: Float?
    var sea_level: Float?
    var grnd_level: Float?
}

struct Wind: Codable {
    var speed: Float?
    var deg: Float?
    var gust: Float?
}

struct Rain: Codable {
    var lastHour: Float?
    var last3Hours: Float?
    
    private enum CodingKeys: String, CodingKey {
        case lastHour = "1h"
        case last3Hours = "3h"
    }
}

struct Snow: Codable {
    var lastHour: Float?
    var last3Hours: Float?
    
    private enum CodingKeys: String, CodingKey {
        case lastHour = "1h"
        case last3Hours = "3h"
    }
}

struct Cloud: Codable {
    var percentage: Int?
    
    private enum CodingKeys: String, CodingKey {
        case percentage = "all"
    }
}

struct Sys: Codable {
    var country: String?
    var sunrise: Int?
    var sunset: Int?
    
    enum CodingKeys: String, CodingKey {
        case country
        case sunrise
        case sunset
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let countryCode = try? container.decode(String.self, forKey: .country) {
            country = countryCodeToFullName(code: countryCode)
        } else {
            country = nil
        }
        sunrise = try? container.decode(Int.self, forKey: .sunrise)
        sunset = try? container.decode(Int.self, forKey: .sunset)
    }
    
    func countryCodeToFullName(code: String?) -> String? {
        guard code != nil else {return nil}
        let current = Locale(identifier: "en_US")
        let country = current.localizedString(forRegionCode: code!)
        return country
    }
}

 

정리할 수 있다. 위 Weather.swift 파일은 코드 상단에 기재된 바와 같이 David Rozmajzl의 구조를 참조하였다.

 

이제 위에서 정리한 방법들을 사용해 UI를 재량껏 꾸미면 나만의 날씨 앱을 제작할 수 있을 것이다.