[Swift] 串接 AccuWeather API,實作一個天氣 APP
會寫這篇文章的起因呢,是一個剛接觸程式語言幾天的學生,很興奮地貼了一篇 AccuWeather API 的教學文章問我,他按照那篇文章有沒有辦法實作出一個天氣 APP ,我看了一下,教學是 Android 阿…
於是,為了滿足這位可愛的學生,我下海了 😂
專案環境
- Xcode10.3
- Swift 5.1
- iOS 12+
# 熱身
先了解一下 API 是什麼。
API (Application Programming Interface) 應用程式介面,是指不同的軟體系統之間,為了溝通,而做出的一個協定,A 定義好介面協定,那麼 B C D 都可以用符合協定的方式取得 A 所提供的資訊。完整的解釋可以看 wiki。
# Ready Go!
Ok,直接開始吧!
這邊我選擇先把畫面建立好,接著再去串 API ,再把 API 取回的資料 Show 在畫面上。
首先建立一個 Single View App專案。
## 畫面的部分
接著直接點開 Main.storyboard
開始設計我們的畫面吧。
建立一個地區的說明文字,我們拉一個 UILabel 來顯示地區,並對他的樣式做一些修改
- 文字大小改大
- 設定文字置中
這邊還有很多設定,有興趣的童鞋可以自己嘗試(對,我在說妳XDD)
再來最重要的:溫度的資訊。這邊為了方便,我們拉兩個 UILabel ,一個放說明,一個放溫度
一樣可以設置成你喜歡的樣式,這邊求方便我直接複製上面的 Label 下來調整大小跟內容而已
℃
這個符號,可以按 control + command + 空白,會彈出一堆符號,往下滾動到類字母符號
的分類,就會有這個溫度單位的符號可以使用囉
畫面部署完成(有夠簡陋的哈哈哈哈),接著就是讓我們的 storyboard
跟 code
做連結。
因為我們需要改變的,只有 地區 跟 溫度的值 而已,所以拉這兩個 Label 即可,溫度:這個說明用的 Label 目前沒有變更的需求,所以在上 storyboard 設定完即可。
OK,畫面的前期工作準備完成了,接下來就是關於 AccuWeather API 的部分了。
# 註冊 AccuWeather API
- 我們到 AccuWeahter Developer 申請帳號,填完資訊之後會收到一封驗證信,接著收信,點連結,會導到網站請你填入一些資訊(密碼、時區等)
-
申請完成之後可以在右上角看到自己的 mail,點右上角 your mail -> My App -> Add a new App,並填寫相關資訊。
- 按下 Create App,就可以看到我們跟 AccuWeather 申請的 App 啦,點開 App 會看到 Accu 發給我們的一組 API Key,這組 Key 是用來跟 Accu 取得天氣資訊時需要傳送給他的一組認證鑰匙。
4. 然後我們到網站的 API Reference
,裡面可以看到 Accu 有提供許多 API 供我們使用,那我們需要的天氣資訊,藏在 Current Conditions API
裡面,花了我不少時間在一個一個測試到底哪一個可以用…
進去之後選第一個 Current Conditions
即可
- 這網站很貼心的直接提供網頁版的測試環境,只要填入資訊,按下
Send this request
就可以發送請求,並且網站會把Request(請求的資訊)
跟Response(回傳的資訊)
都印出來給我們看。比較特別的是
v1/
後面需要填入 locationKey 這邊需要呼叫這個網站的別支 API 來取得,那為了方便,我直接找到 台北市的 locationKey 給大家使用了 315078
測試完畢,確認過眼神,確認過這支 API 有我們要的天氣資訊,接下來回到程式囉。
## 建立接收的 Model (模型)
為了接收 API Response 的資訊,我們建立一個 Model ,這部分牽扯到比較多的觀念,不清楚的童鞋可以先選擇複製貼上即可。
這樣做的目的是方便我們在使用 API 回來的資訊時,可以用事先宣告好的變數,去撈取資訊
- 養成好習慣,我們為 Model 建立一個資料夾做分類
- 然後在 Model 資料夾下面新增一個檔案
-
選擇 Swift File ,這是建立一個空的檔案,副檔名為 .swift ,之後輸入檔案名稱
WeatherModel.swift
,然後按下 create 就建立完成啦! -
程式碼的部分如下:
12345678910111213141516171819202122232425262728293031323334353637383940import Foundationstruct WeatherModel: Decodable {let localObservationDateTime: Stringlet epochTime: TimeIntervallet weatherText: Stringlet isDayTime: Boolstruct Temperature: Decodable {struct Metric: Decodable {let value: Doublelet unit: Stringlet unitType: Intprivate enum CodingKeys: String, CodingKey {case value = "Value"case unit = "Unit"case unitType = "UnitType"}}let metric: Metricprivate enum CodingKeys: String, CodingKey {case metric = "Metric"}}let temperature: Temperaturelet mobileLink: URLlet link: URLprivate enum CodingKeys: String, CodingKey {case localObservationDateTime = "LocalObservationDateTime"case epochTime = "EpochTime"case weatherText = "WeatherText"case isDayTime = "IsDayTime"case temperature = "Temperature"case mobileLink = "MobileLink"case link = "Link"}}
接收的 Model 建立完成之後,就是重頭戲,向 AccuWeather 發出請求了。
- 宣告一個變數,將我們剛剛在網站註冊取得的 APIKey 儲存起來。
-
建立一個
private func callAPI() {}
,並在viewDidLoad
的時候呼叫這個 func123456789101112131415161718192021class ViewController: UIViewController {@IBOutlet weak var locationLabel: UILabel!@IBOutlet weak var tmpLabel: UILabel!// 填入你的 APIKeylet apiKey = "Nx8f****************hHK"override func viewDidLoad() {super.viewDidLoad()// 這邊是呼叫函數callAPI()}// 這邊是建立函數private func callAPI() {}} - 在
func callAPI()
這個函數裡面,實作發出請求的動作吧。1234567891011121314151617181920212223242526272829303132333435363738private func callAPI() {// 根據網站的 Request tab info 我們拼出請求的網址let url = URL(string: "https://dataservice.accuweather.com/currentconditions/v1/315078?apikey=\(apiKey)&language=zh-Tw")!// 將網址組成一個 URLRequestvar request = URLRequest(url: url)// 設置請求的方法為 GETrequest.httpMethod = "GET"// 建立 URLSessionlet session = URLSession.shared// 使用 sesstion + request 組成一個 task// 並設置好,當收到回應時,需要處理的動作let dataTask = session.dataTask(with: request, completionHandler: { (data, response, error) -> Void in// 這邊是收到回應時會執行的 code// 因為 data 是 optional,有可能請求失敗,導致 data 是空的// 如果是空的,我們直接 return,不做接下來的動作guard let data = data else {return}do {// 使用 JSONDecoder 去解開 datalet weatherModel = try JSONDecoder().decode([WeatherModel].self, from: data)print(weatherModel)} catch {print(error)}})// 啟動 taskdataTask.resume()}
這時候可以 Run 一下,如果一切正常,應該會在 log 看到類似下面的資訊,表示一切正常,我們成功地拿到回應,並將回應解析成我們的 WeatherModel。
# 把資料 Show 在畫面上囉
關鍵時刻來了!我們要把資料 Show 在畫面上!
我們在 print(weatherModele)
這行程式後面,加上一點改變畫面的 code。
1 2 3 4 5 6 7 8 |
// 改變 UI 的動作必須在主線程完成,所以將下面的 code 包在 DispatchQueue.main.async 的大括號裡面 DispatchQueue.main.async { self.locationLabel.text = "台北市" // 因為我們用 Array<WeatherModel> 去解析 data,所以在使用的時候我們先取出第一筆資料。 let tmp = weatherModel.first?.temperature.metric.value ?? -1 self.tmpLabel.text = "\(tmp)℃" } |
接著在 Run 一下,完美!
完整的專案在 GitHub