API를 활용해 JSON형식으로 된 날씨 정보를 가져오기
protocol을 extension해서 delegate패턴 사용하기
148강 api key를 발급받는 데 시간이 걸릴 수 있으므로 먼저 들어두자
https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/
애플에서 자동으로 크기와 정렬을 맞춰주는 아이콘을 제공
다크모드로 변경도 가능
커스텀컬러 팔레트 만들려면 드래그 해주기
확대하면 픽셀이 깨짐
확대해도 픽셀이 안깨짐
Assests.xcassets> background 변경> Resizing: Preserve Vector Data 체크 > Scales: Single Scale> Appearances: Any, Light, Dark
Main.Storyboard에서는 선택한 이미지사이즈가 고정되어서 깨져보이지만
시뮬레이터로 실행하면 안깨진다
사용자의 텍스트를 입력받음
검색창으로 사용
Text Input Traits
Capitalization: Words
Return Key: Go
키보드: I/O > Keyboard > Toggle Software Keyboard (command K)
searchTextField.delegate = self
UITextField가 viewController(self)를 delegate(위임하다)해서
텍스트필드에 무슨 일이 발생하면 뷰컨트롤러가 알아챈다
뷰 컨트롤러가 위임받는다
textFieldShouldReturn
텍스트필드의 리턴버튼을 누를지 말지 delegate(뷰컨트롤러)한테 물어본다
import UIKit
class WeatherViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var conditionImageView: UIImageView!
@IBOutlet weak var temperatureLabel: UILabel!
@IBOutlet weak var cityLabel: UILabel!
@IBOutlet weak var searchTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//텍스트필드가 뷰컨트롤러에 무슨 일이 일어났는지 알려준다
searchTextField.delegate = self
}
@IBAction func searchPressed(_ sender: UIButton) {
searchTextField.endEditing(true) //키보드 숨기기
}
//키보드의 리턴을 사용해야하나요? 옙!
//💥(_ textField: UITextField) 여기를 주의깊게 살펴보면,
//화면에 텍스트필드가 여러개여도 텍스트필드마다 작업을 따로 설정해줄 필요가 없어진다💥
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
searchTextField.endEditing(true) //키보드 숨기기
return true
}
//shoud -> asking for permission
//유효성검사에 유용하다
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if textField.text != "" {
return true
} else {
textField.placeholder = "Type something"
return false
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
searchTextField.text = "" //텍스트필드 비우기
}
}
https://developer.apple.com/documentation/uikit/uitextfielddelegate
https://openweathermap.org/api
https://openweathermap.org/current
JSON data를 tree structure로 보려면
JSON Viewer Awesome를 크롬 extension을 다운받자(Pro로 바뀌었넴)
그냥 json 검색해서 아무거나 다운 받았다
크롬 익스텐션 말고
그냥 웹사이트 추천
https://jsonlint.com
// ViewController.swift
import UIKit
class WeatherViewController: UIViewController, UITextFieldDelegate {
var weatherManager = WeatherManager()
func textFieldDidEndEditing(_ textField: UITextField) {
if let city = searchTextField.text {
weatherManager.fetchWeather(cityName: city)
}
searchTextField.text = "" //텍스트필드 비우기
}
}
// WeatherManager.swift
import Foundation
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=나의에이피아이&units=metric"
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
//1. create a URL
if let url = URL(string: urlString) {
//2. create a URLSession
let session = URLSession(configuration: .default) //브라우저
//3. give the sessiona task
let task = session.dataTask(with: url, completionHandler: handle(data:response:error:))
//4. start the task
task.resume()
}
}
func handle(data: Data?, response: URLResponse?, error: Error?) {
if error != nil {
print(error!)
return
}
if let safeData = data {
let dataString = String(data: safeData, encoding: .utf8)
print(dataString)
}
}
}
The resource could not be loaded
because the App Transport Security policy requires the use of a secure connection
클로저: 이름이 없는 익명함수, self-contained한 함수로 매개변수로 넘길 수 있다, 리턴값으로 받을 수 있다
다양한 형태로 축약이 가능한데, 간결성과 가독성을 고려해야한다.
simplicity간결성 vs readibility가독성
func functionName(parameter: parameterType) -> returnType {
return output
}
func calculator(n1: Int, n2: Int, operation: (Int, Int) -> Int) -> Int {
return operation(n1, n2) //call
}
// func add(no1: Int, no2: Int) -> Int {
// return no1 + no2
// }
// // 클로저로 변환
// { (no1: Int, no2: Int) -> Int in
// return no1 + no2
// }
// data type inference 데이터 타입 추론
// n1, n2, return type의 데이터 타입 생략 가능
// calculator(n1: 2, n2: 3, operation: { (no1, no2) in no1 + no2 })
// $0: 첫번째 파라미터, $1: 두번째 파라미터
// calculator(n1: 2, n2: 3, operation: { $0 + $1 })
// trailing closure
let result = calculator(n1: 2, n2: 3) { $0 + $1 }
print(result)
calculator(n1: 2, n2: 3, operation: { (no1: Int, no2: Int) -> Int in
return no1 + no2
})
// 함수를 매개변수로 사용할 때
func multiply(no1: Int, no2: Int) -> Int {
return no1 * no2
}
calculator(n1: 2, n2: 3, operation: multiply)
// map reduce filter
// stringfy
let array = [1, 2, 3, 4, 5]
print(array.map{"\($0)"})
//위의 코드에 클로저 적용하기
//handle함수를 task가 실행되면 실행되도록 클로저 형식으로 나타내기
//3. give the sessiona task
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
return
}
if let safeData = data {
let dataString = String(data: safeData, encoding: .utf8)
}
}
https://developer.apple.com/documentation/swift/array/3017522-map
https://docs.swift.org/swift-book/LanguageGuide/Closures.html
computed property는 계산에 따라 값이 바뀌기 때문에 var 키워드를 써줘야 한다
struct myModel {
//stored property
let myName: String
let myAge: Int
//computed property
var myGeneration: String {
switch myAge:
case 0...19:
return "어린이"
case 20...:
return "어른"
}
}
//WeatherManager.swift
import Foundation
struct WeatherManager {
//3. give the sessiona task
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
return
}
//💥JSON파싱 눈여겨보기
if let safeData = data {
//let dataString = String(data: safeData, encoding: .utf8)
parseJSON(weatherData: safeData)
}
}
}
}
func parseJSON(weatherData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
print(decodedData.name)
print(decodedData.main.temp)
print(decodedData.weather[0].description)
} catch {
print(error)
}
}
}
//WeatherData.swift
//JSON 데이터를 파싱하기 위해서
import Foundation
//{
// "weather": [{
// "id": 801,
// "main": "Clouds",
// "description": "few clouds",
// "icon": "02d"
// }],
// "main": {
// "temp": 19.76
// },
// "name": "London"
//}
struct WeatherData: Decodable {
let name: String
let main: Main
let weather: [Weather]
}
struct Main: Decodable {
let temp: Double //JSON데이터와 프로퍼티의 이름이 정확히 일치해야한다
}
struct Weather: Decodable {
let description: String
let id: Int
}
WeatherManager: 데이터를 가져오기 위한 메서드를 사용하는 공간
WeatherData: JSON 파싱하기 위해 필요한 데이터
WeatherModel: 화면에 뿌리기 위해 필요한 데이터
//WeatherManager.swift
func parseJSON(weatherData: Data) -> WeatherModel? {
let decoder = JSONDecoder()
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 {
print(error)
return nil
}
}
// WeatherModel.swift
import Foundation
struct WeatherModel {
//Stored Property
let conditionId: Int
let cityName: String
let temperature: Double
//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"
}
}
var temperatureString: String {
return String(format: "%.1f", temperature)
}
}
WeatherManager를 통해서 세션을 연결하고
WeatherData를 통해 데이터를 파싱하고
WeatherModel에 이쁘게 데이터를 저장했는데...
💥WeatherModel에 저장한 데이터를 뷰컨트롤러에 띄워야 한다
프로토콜을 만들 때는 textField의 파라미터 이름과 같은 형식으로 만드는 것이 일반적이다.
어떻게 하면 좋을까?
// WeatherManager.swift
// 프로토콜 만들기
protocol WeatherManagerDelegate {
func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel)
}
struct WeatherManager {
//delegate 만들기
var delegate: WeatherManagerDelegate?
func performRequest(urlString: String) {
if let safeData = data {
//let dataString = String(data: safeData, encoding: .utf8)
if let weather = self.parseJSON(weatherData: safeData) {
//여기서 만약 보여줄 뷰컨트롤러의 인스턴스를 생성했으면 그 뷰컨트롤러에서만 WeatherManager를 사용할 수 있기 때문에 delegate 패턴을 사용한다
delegate?.didUpdateWeather(self, weather: weather)
}
}
}
}
}
}
// ViewController.swift
import UIKit
class WeatherViewController: UIViewController, WeatherManagerDelegate { //상속받기
//구현하기
func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
print(weather.cityName)
}
var weatherManager = WeatherManager()
override func viewDidLoad() {
//위임하기
weatherManager.delegate = self
}
}
UILabel.text must be used from main thread only
UI(뷰컨트롤러에 있는 Lable)에 data를 넣으려고 했을 때
data를 가져오는 과정에서 (session task과 parseJSON 실행해야 함)
세션 안에서 API로 네트워킹 및 데이터 파싱을 클로저로 받아 콜백함수(completion handler)를 사용했기 때문에 이걸 다 기다리면 시간이 오래걸리기 때문이다.
func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
DispatchQueue.main.async {
self.temperatureLabel.text = weather.temperatureString
self.conditionImageView.image = UIImage(systemName: weather.conditionName)
self.cityLabel.text = weather.cityName
}
}
https://developer.apple.com/documentation/foundation/thread
https://www.coinapi.io/Pricing