前面介紹了 Swift 串接 API 下載資料,以及如何解析JSON 與 XML 資料了。
那這次我們就來製作 火車動態即時查詢 的APP吧!!!
本篇內容包含:
- 串接 PTX API — 到 PTX 申請 APPID 和 APPKey
- 如何設定 HTTP header — Authorization 和 x-date 欄位
- 取得所有站名,顯示在 Table View 中
- UISearchController 搭配 Table View — 用來搜尋火車站
- 取得車站的動態即時到站時刻 — 車次是否誤點 (動態前後30分鐘的車次)
- 最後會附上 Github 連結 (在最下面)
先附上成果~~~
讓我們開始吧~~~
串接 PTX API — 到 PTX 申請 APPID 和 APPKey
到公共運輸整合資訊流通服務平台 申請會員!!!
申請一般會員就可以囉!!!將欄位填寫完畢後送出審查,通常不到一天就可以在信箱中收到結果了,通過的話則會收到 APP ID 和 APP Key。
如何設定 HTTP header — Authorization 和 x-date 欄位
詳細授權驗證可以到以下網站查看
首先是取得時間x-date — 發送 request 的時間,定義一個 function getTimeString PTX API要求格式的時間字串。
func getTimeString() -> String {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "EEE, dd MMM yyyy HH:mm:ww zzz"
dateFormater.locale = Locale(identifier: "en_US")
dateFormater.timeZone = TimeZone(secondsFromGMT: 0)
return dateFormater.string(from: Date())
}
接下來設定 HMAC & SHA 加密。import CryptoKit,可以方便寫加密相關程式。官網有提供 swift 的程式範例 ,可以到下列網址參考。
但官網提供的加密方式採用 SHA1,但是 SHA1 比較不安全,我使用的是SHA256,也提供各位不同的加密方式作參考~~~
設定 HTTP header Authorization 和 x-date 欄位的完整程式如下:
APP_ID 和 APP_KEY,要輸入自己申請的 ID 和 KEY 喔~~~
let APP_ID = "自己申請的ID"
let APP_KEY = "自己申請的KEY"let xdate = getTimeString() // 取得時間字串func
let signDate = "x-date: " + xdate;let key = SymmetricKey(data: Data(APP_KEY.utf8))
let hmac = HMAC<SHA256>.authenticationCode(for: Data(signDate.utf8), using: key)
let base64HmacString = Data(hmac).base64EncodedString()
let authorization = """
hmac username="\(APP_ID)", algorithm="hmac-sha256", headers="x-date", signature="\(base64HmacString)"
"""
設定完後,就可以產生 URLRequest ,產生 URLSessionDataTask 抓取資料。
// 取得全部車站站名
let url = URL(string: "https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/Station?$format=JSON")!var request = URLRequest(url: url)
request.setValue(xdate, forHTTPHeaderField: "x-date")
request.setValue(authorization, forHTTPHeaderField: "Authorization")
request.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")// URLSessionDataTask 抓取資料
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let loadedData = data {
do{
// 使用JSONDecoder解析Json格式
let stationData = try JSONDecoder().decode([Station].self, from: loadedData)
self.stations = stationData
//寫入Stations資料 將清單存到手機中
Station.saveToFile(stations: self.stations)
}catch{
print(error.localizedDescription)
}
}
}.resume()
如何解析 JSON 資料 可以看先前發過的這篇文章~~~
我在解析資料的同時,也將全部車站站名 使用 UserDefaults 存起來。
struct Station:Codable {
var StationID: String
var StationName: NameType static func saveToFile(stations:[Station]){
let saveStations = try? JSONEncoder().encode(stations)
UserDefaults.standard.set(saveStations, forKey: "Stations")
} static func loadFromFile() -> [Station]?{
guard let loadDate = UserDefaults.standard.data(forKey: "Stations") else {return nil}
return try? JSONDecoder().decode([Station].self, from: loadDate)
}
}struct NameType:Codable {
var Zh_tw: String
var En: String
}
UISearchController 搭配 Table View — 用來搜尋火車站
上述做完 URLSessionDataTask 解析完 JSON 資料後,將車站站名以及ID 存進 stations 中,接著在 Table View 中顯示。但是車站那麼多,我們慢慢滑的話會很耗時間,這時我們就需要 UISearchController 來搭配 Table View
來做 Table View 的搜尋列喔~~~
- 先宣告 var searchController: UISearchController?
- 設置兩個存放的陣列
- 一個是全部的車站 var stations = [Station]()
- 一個是輸入文字後,搜尋到的車站 var searchStations = [Station]()
再來 建立設置 UISearchController 的function
- 初始化SearchController
searchController = UISearchController(searchResultsController: nil)
2. 增加 UISearchResultsUpdating 的 delegate
class TRAStationTableViewController: UITableViewController, UISearchResultsUpdating {
並將指派屬性
searchController?.searchResultsUpdater = self
3. 在 navigation 中加入 searchController 搜尋列
navigationItem.searchController = searchController
接下來設置 Table View 的 numberOfRowsInSection 和 cellForRowAt
簡單來說就是 偵測 searchController 是否有被執行,如果有就顯示 搜尋到的車站 searchStations 。
最後就是如何 更新搜尋過後的 Table View ,要來比對字串!
使用 .filter
這樣基本搜尋功能就完成囉~~~
取得車站的動態即時到站時刻 — 車次是否誤點 (動態前後30分鐘的車次)
最後就是點選車站,顯示 動態前後30分鐘的車次 是否誤點。
在前一頁面的 Table View 需要傳值到下一頁。
接著車次的資訊 我們資料格式為
- 車站 ID
- 車站站名
- 方向 (北上南下)
- 火車種類 (區間、自強、莒光)
- 終點站
- 表定抵達時間
- 誤點 (0:準點、≥1 誤點幾分鐘)
下載的方式跟前面一樣,網址部分帶入 stationID
最後就一點~文字顯示格式的部分啦~~~美化一下介面文字
車次時間
車次 準點或誤點的提示
利用 layer.masksToBounds = true
以及 layer.cornerRadius = 10 來設置背景圓角~~~
最後附上完整 Github 連結~~~