---
title: "Swift로 미세먼지 앱 만들기"
description: "현재 위치에서 가까운 측정소의 미세먼지 정보를 보여주는 Swift 앱 개발 기록. 공공 데이터와 위치 정보를 앱 화면에 연결했습니다."
canonical: https://blog.skyline23.com/2
language: ko
published: 2021-04-10T17:19:25+09:00
modified: 2022-05-23T15:23:13+09:00
---

# Swift로 미세먼지 앱 만들기

![](/blog-covers/air-quality-1536.webp)

학교 과제로 미세먼지를 보여주는 프로그램을 만들라기에 자신감 있게 iOS를 선택했습니다!

~할 줄 아는게 iOS밖에 없어서...~

과제도 이왕 만드는김에 제대로 만들어보자라고 생각해서 앱의 장점인 현재 위치를 받아와 주변 측정소의 미세먼지 값을 쓰기로 했어요.

![앱 미리보기](/blog-assets/b2e59657095ff5d7362e3729.gif)

앱 미리보기

미세먼지 값을 받아오려면 네트워크 통신이 필수적이었고, 현재 위치를 알아내야 했습니다.

그래서 서버와의 통신 전, 서버의 요청값에 보내줄 현재위치를 알아 내기 위해서 CoreLocation을 사용했어요.

![info.plist에 먼저 위치정보 접근 허용을 받을때 이유를 설명해주기 위한 문자열을 추가한다.](/blog-assets/852abaf82f19aa26c7d4b0a4.webp)

info.plist에 먼저 위치정보 접근 허용을 받을때 이유를 설명해주기 위한 문자열을 추가한다.

```swift
// lazy로 선언하여 메모리 관리
    lazy var locationManager = CLLocationManager().then {
        // 10미터 이내의 정확도로 설정을 하여 배터리 관리 최적화
        $0.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        // 거리 위치 변화를 감지하지 않음
        $0.distanceFilter = kCLHeadingFilterNone
        // 권한을 요청
        $0.requestWhenInUseAuthorization()
    }
```

그리고 locationMagager 인스턴스를 생성하고 몇가지 조건을 추가 해주었습니다.

```swift
    // 정보를 담을 struct 설정
    struct userLocation {
        var latitude: Double!
        var longitude: Double!
    }
    
    // 현재 좌표값 가져오기
   var info = userLocation()
   let coord = locationManager.location?.coordinate
   info.latitude = coord?.latitude
   info.longitude = coord?.longitude
```

그 후, 정보를 담을 struct를 만들고, 현재 좌표값을 받아 구조체에 넣어주었습니다.

그 후 문제가 생겼는데, 제가 사용할 API에서 넘겨줘야할 좌표값이 다르더라구요!

아래는 사용한 API주소인데, iOS에서 받아오는 좌표값은 WGS84 좌표값이었고, 근처 측정소를 찾기 위해선 TM좌표로 변환해야했습니다.

측정소 정보 : [www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15073877](https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15073877)

[공공데이터 포털](https://www.data.go.kr/)

미세먼지 값 정보 : [www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15073861](https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15073861)

[공공데이터 포털](https://www.data.go.kr/)

그래서 WGS84좌표값들을 변환해주기 위해 카카오 API를 사용했습니다.

이거 말고도 로컬로 변경할 수 있는 Extension이 있던데 이건 나중에 따로 공부해봐야 할 것 같네요...

[developers.kakao.com/docs/latest/ko/local/dev-guide#trans-coord](https://developers.kakao.com/docs/latest/ko/local/dev-guide#trans-coord)

[Kakao Developers](https://developers.kakao.com/)

```swift
// TM좌표로 변환
    func TM(url: String, longitude: Double, latitude: Double, handler: @escaping(userLocation) -> Void) {
        var result = userLocation()
        let headers:HTTPHeaders = ["Authorization" : kakaoKey]
        let parameters: Parameters = ["x" : longitude, "y" : latitude, "output_coord" : "TM"]
        let alamo = AF.request(url, method: .get,parameters: parameters, encoding: URLEncoding.queryString ,headers: headers)
        alamo.responseJSON() { response in
            switch response.result {
            case .success(let value):
                let json = JSON(value)
                let documents = json["documents"].arrayValue
                result.longitude = documents[0]["x"].double
                result.latitude = documents[0]["y"].double
                handler(result)
            case .failure(_):
                let alert = UIAlertController(title: nil, message: "네트워크를 다시 확인해주세요", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
                return
            }
        }
    }
```

먼저 카카오 API를 통해 반환된 좌표값을 콜백 함수를 통해 빼내고, 근처 측정소를 받아오는 API에 값을 다시 넘겨주었습니다.

```swift
// 근처 측정소 받아오기
    func getNearbyMsrstn(url: String, tmX: Double, tmY: Double, handler: @escaping(String) -> Void) {
        let parameters: Parameters = [
            "serviceKey" : airKoreaKey,
            "tmX" : tmX,
            "tmY" : tmY,
            "returnType" : "json"
        ]
        
        let alamo = AF.request(url, method: .get,parameters: parameters, encoding: URLEncoding.default)
        alamo.responseJSON() { response in
            switch response.result {
            case .success(let value):
                let json = JSON(value)
                let stationName = json["response"]["body"]["items"][0]["stationName"].string!
                locationInfo.shared.stationName = stationName
                handler(stationName)
            case .failure(_):
                let alert = UIAlertController(title: nil, message: "네트워크를 다시 확인해주세요", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
                return
            }
        }
    }
```

그 후 측정소의 이름을 활용해 제가 필요한 정보(미세먼지 값)을 받아오는 API를 한번 더 요청해 주었어요.

```swift
// 미세먼지 값 받아오기
    func getfinedust(url: String, stationName: String, handler: @escaping(String, String) -> Void) {
        let parameters: Parameters = [
            "serviceKey" : airKoreaKey,
            "stationName" : stationName,
            "dataTerm" : "DAILY",
            "informCode" : "PM10",
            "returnType" : "json"
        ]
        
        let alamo = AF.request(url, method: .get,parameters: parameters, encoding: URLEncoding.default)
        alamo.responseJSON() { response in
            switch response.result {
            case .success(let value):
                let json = JSON(value)
                let pm10Value = json["response"]["body"]["items"][0]["pm10Value"].string!
                let dataTime = json["response"]["body"]["items"][0]["dataTime"].string!
                locationInfo.shared.pmValue = pm10Value
                locationInfo.shared.dataTime = dataTime
                handler(pm10Value, dataTime)
            case .failure(_):
                let alert = UIAlertController(title: nil, message: "네트워크를 다시 확인해주세요", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
                return
            }
        }
    }
```

이번에 로딩화면을 활용해보고 싶어서 이러한 처리를 로딩화면에서 하게 만들어주었는데, 그렇게 좋지는 못한 것 같네요.

로딩화면에서 정보를 다 처리한 후에 싱글톤 패턴을 이용하여 값을 저장하고, 그 값을 메인 화면에 값을 넘겨 주었습니다.

```swift
import Foundation


class locationInfo {
    static let shared = locationInfo()
    
    var nowLocationName: String?
    var longitude: Double?
    var latitude: Double?
    var pmValue: String?
    var dataTime: String?
    var stationName: String?

    private init() { }
}
```

확실히 비효율적인 로직이 많아보이는데, 더 공부해야할 게 많아보이네요.

그래도 싱글톤 패턴이라던지 Escaping Closure, CoreLocation등 처음 써본것들이 많아서 신기했고, Alamofire를 활용하는 법을 조금 더 배운거 같아 의미있는 프로젝트였던 것 같습니다.
