第13部:CoreLocationと完全な注釈付きコード(復習)を使用する


settings


Privacy> Location services> app
デバッグ>Location>シミュレータの位置の決定方法
ここで変更した後、変更した値を決定するには、アプリケーションを削除して再実行する必要があります.

.plist


Property List
すべてのXcodeプロジェクトで自動生成
アプリケーション実行時の設定情報(アプリケーション実行時)を含む
key-valuepairとして値を格納
key: property name
value: configuration
Setting a property

Change reflected in plist

ロケーションマネージャを使用する場合


権限要求メッセージの送信時に追加する必要があります
+キーを押して「ユーザー使用状況」Descriptionを選択し、Valueにコメントを書く
https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html

//  ViewController.swift

import CoreLocation

class WeatherViewController: UIViewController {

    let locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 위치 요청 전에 미리 위임해야 한다
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestLocation()   //one-time 한 번만 call
    }
    
    @IBAction func locationPressed(_ sender: Any) {
        locationManager.requestLocation()
    }
}

//MARK: - CLLocationManagerDelegate
extension WeatherViewController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            locationManager.stopUpdatingLocation()  //로케이션매니저 종료하기
            let lat = location.coordinate.latitude
            let lon = location.coordinate.longitude
            weatherManager.fetchWeather(latitude: lat, longitute: lon)
        }
    }
    
    // 조건에 따라 에러 메서드(아래)도 반드시 구현해야 한다
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
}
//  WeatherManager.swift
    
    func fetchWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
        let urlString = "\(weatherURL)&lat=\(latitude)&lon=\(longitude)"
        performRequest(with: urlString)
    }

コメント付き完全コード

//  WeatherViewController.swift

import UIKit
import CoreLocation

class WeatherViewController: UIViewController {

    @IBOutlet weak var conditionImageView: UIImageView!
    @IBOutlet weak var temperatureLabel: UILabel!
    @IBOutlet weak var cityLabel: UILabel!
    @IBOutlet weak var searchTextField: UITextField!
    @IBOutlet weak var currentLocation: UIButton!
    
    
    var weatherManager = WeatherManager()
    let locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 위치 요청 전에 미리 위임해야 한다
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestLocation()   //one-time 한 번만
        //locationManager.startUpdatingLocation() //내비게이션처럼 이동하는 정보를 계속 수집
        
        // WeatherViewController가 weatherManager를 위임한다
        weatherManager.delegate = self
        searchTextField.delegate = self
    }
    
    @IBAction func locationPressed(_ sender: UIButton) {
        locationManager.requestLocation()
    }
    
}

//MARK: - UITextFieldDelegate, WeatherManagerDelegate

extension WeatherViewController: UITextFieldDelegate {
    
    @IBAction func searchPressed(_ sender: UIButton) {
        //키보드 해제
        searchTextField.endEditing(true)
        print(searchTextField.text!)
        
    }
    
    // should가 들어가면 bool을 return받아서 행동을 정한다
    // return(Go)를 눌렀을 때
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        searchTextField.endEditing(true)
        print(searchTextField.text!)
        return true
    }
    
    // 유효성 검사
    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        // 아무것도 없으면 멈추고, 아니면 Type something 보여주기
        if textField.text != "" {
            return true
        } else {
            textField.placeholder = "Type something"
            return false
        }
    }
    
    // 텍스트 필드에 글자 입력 후 return이나 버튼을 눌러 입력한 후
    // 키보드를 해제했을 때 텍스트 필드 공백으로 만들기
    func textFieldDidEndEditing(_ textField: UITextField) {
        if let city = searchTextField.text {
            weatherManager.fetchWeather(cityName: city)
        }
        
        searchTextField.text = ""
    }
}

//MARK: - WeatherManagerDelegate

extension WeatherViewController: WeatherManagerDelegate {
    // delegate method의 parameter규칙
    // argument(밖에서 호출), parameter(안에서 사용, external과 internal이 있음)
    // delegate method를 생성시킨 data type WeatherManager를 앞에 써주고 external parameter는 생략표시(_) 한다
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
        // 클로저 안에서는 self를 써준다
        DispatchQueue.main.async {
            self.temperatureLabel.text = weather.temperatureString
            self.conditionImageView.image = UIImage(systemName: weather.conditionName)
            self.cityLabel.text = weather.cityName
        }
        
    }
    
    func didFailWithError(error: Error) {
        print(error)
    }
}

//MARK: - CLLocationManagerDelegate

extension WeatherViewController: CLLocationManagerDelegate {
    //CL core Location
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            locationManager.stopUpdatingLocation()
            let lat = location.coordinate.latitude
            let lon = location.coordinate.longitude
            weatherManager.fetchWeather(latitude: lat, longitude: lon)
        }
    }
    
    // 조건에 따라 에러 메서드(아래)도 반드시 구현해야 한다
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
}
WeatherManager.swift
//
//  WeatherManager.swift
//  Clima
//
//  Created by  Etoday on 2021/02/15.
//  Copyright © 2021 App Brewery. All rights reserved.
//

import Foundation
import CoreLocation

protocol WeatherManagerDelegate {
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel)
    func didFailWithError(error: Error)
}

struct WeatherManager {
    let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=42250e4b3738d98ef5dae4a4635b68fd&units=metric"
    
    var delegate: WeatherManagerDelegate?
    
    func fetchWeather(cityName: String) {
        let urlString = "\(weatherURL)&q=\(cityName)"
        performRequest(with: urlString)
    }
    
    func fetchWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
        let urlString = "\(weatherURL)&lat=\(latitude)&lon=\(longitude)"
        performRequest(with: urlString)
    }
    
    func performRequest(with urlString: String) {
        //1. Create a URL
        if let url = URL(string: urlString) {
            // 2. Create a URLSession 기본 설정(브라우저)
            let session = URLSession(configuration: .default)
            
            // 3. Give the session a task
            let task = session.dataTask(with: url) { (data, response, error) in
                // 에러 처리
                if error != nil {
                    self.delegate?.didFailWithError(error: error!)
                    return  // exit
                }
                // parse into JSON format 파싱해서 delegate를 활용해서 WeatherViewController에 띄우기
                if let safeData = data {
                    // 클로저 안에서 메서드를 호출할 때는 self를 써줘야 한다
                    // 파싱이 성공했을 때 delegate가 있으면 didUpdateWeather를 실행시킨다
                    if let weather = self.parseJSON(safeData) {
                        self.delegate?.didUpdateWeather(self, weather: weather)
                    }
                }
            }
            // 4. Start the task
            task.resume()
        }
    }
    
    func parseJSON(_ weatherData: Data) -> WeatherModel? {
        let decoder = JSONDecoder()
        // input으로 object가 아니라 type을 받는데, .self를 붙여 type object의 참조값을 사용한다
        // WeatherData structure을 사용해 decode를 시도한다
        do {
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
            let id = decodedData.weather[0].id
            let temp = decodedData.main.temp
            let name = decodedData.name
            
            let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
            return weather
            
        } catch {
            delegate?.didFailWithError(error: error)
            return nil
        }
    }
 

}

WeatherData.swift
//
//  WeatherData.swift
//  Clima
//  여기는 JSON Parsing하기 위한 곳
//  Created by  Etoday on 2021/02/15.
//  Copyright © 2021 App Brewery. All rights reserved.
//

import Foundation

//Decodable+Encodable -> Codable
struct WeatherData: Codable {
    let name: String
    
//  JSON의 일부분 (예시)
//    main안에는 object{}가 들어있음
//        "main": {
//            "temp": 23.48,
//            "pressure": 1024,
//            "humidity": 40,
//            "temp_min": 21,
//            "temp_max": 25
//        }
    let main: Main
    
//    weather안에는 [] 배열이 들어있음
//    "weather": [
//        {
//          "id": 803,
//          "main": "Clouds",
//          "description": "broken clouds",
//          "icon": "04n"
//        }
//      ]
    let weather: [Weather]
}

// 변수명이 JSON의 변수명과 정확히 일치해야 한다
struct Main: Codable {
    let temp: Double
}

struct Weather: Codable {
    let description: String
    let id: Int
}
WeatherModel.swift
//
//  WeatherModel.swift
//  Clima
//  여기는 화면에 보여줄 데이터를 계산하기 위한 곳
//  Created by  Etoday on 2021/02/15.
//  Copyright © 2021 App Brewery. All rights reserved.
//

import Foundation

struct WeatherModel {
    //stored property
    public let conditionId: Int
    public let cityName: String
    public let temperature: Double
    
    public var temperatureString: String {
        return String(format: "%.1f", temperature)
    }
    
    //computed property (var 사용)
    var conditionName: String {
        switch conditionId {
            case 200...232:
                return "cloud.bolt"
            case 300...321:
                return "cloud.drizzle"
            case 500...531:
                return "cloud.rain"
            case 600...622:
                return "cloud.snow"
            case 701...781:
                return "cloud.fog"
            case 800:
                return "sun.max"
            case 801...804:
                return "cloud.bolt"
            default:
                return "cloud"
        }
    }
    
}