Skip to content

chuntailin/TaipeiAPI

Repository files navigation

<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"></script>

介紹與特色

TaipeiAPI 是串接台北市政府 Open data API 的程式庫,獲取台北市政府的捷運施工資訊,並在 MapView 上用 Annotation 標明各個施工地點的位置。當使用者點擊任意一個施工地點的 Annotation 時,APP 下方會計算出使用者的目前位置與所選的施工地點之間的距離以及預估開車會到達的時間,而在點選 Annotation 上的 Detail Button 時,會顯示使用者所選的施工地點的詳細資訊


架構與實作方法

CocoaPod 套件管理

由於 TaipeiAPI 有使用到第三方的套件,像是 AlamofireSwiftyJSON 等等,故使用 CocoaPod 來管理這些套件

Note: 如果要執行這個Project,請先執行$ pod install

Step 1. - HTTP Request

在 api 串接的部分,首先建了繼承自 URLRequestConvertible 協定的 enum: Router,裡面有一個 static property : baseURLString

static let baseURLString : String

與兩個computed property :

  • method : 定義API的方式 (GET, POST, PUT …)
  • path : 定義API不同的PATH
var method: Alamofire.Method{
        switch self {
        case .APICase(let API):
            switch API {
            case .Function:
                return ...
            }
        }
    }
var path: String{
        switch self {
        case .APICase(let API):
            switch API {
            case .Function:
                return ...
            }
        }
    }

並且 Router 裡也定義了 enum 底下不同的 api case 所回傳對應的 request

var URLRequest: NSMutableURLRequest{
    let mutableURLRequest = NSMutableURLRequest(URL: url)
        switch self {
        case .APICase(let API):
            switch API {
            case .Function :
                    .
                    .
                    .
                return mutableURLRequest
            }
        }

    }

最後,當需要串接 api 時,由 ServerManager 來發出 HTTP request

Alamofire.request(api).responseJSON { (response) in
    switch response.result {
            case .Success(let value): ...
            case .Failure(let error): ...
    }
}

Alamofire 參考資料:這裡

Step 2. - Parse JSON

首先,建了一個 Class : Site 程式碼如下:

class Site: NSObject {
    init(json:JSON){
        let cityName = json["C_NAME"].string
        let address = json["ADDR"].string
            .
            .
            .
        let xString = json["X"].stringValue
        let yString = json["Y"].stringValue

Note:從 Data.Taipei 所接收到的 X, Y 為二度分帶座標,需要再轉為經緯度。轉換的過程參考了 GATool 的寫法,並改寫成 Swift 格式

ServerManager 發出 HTTP request 後,將 Server 所回傳的 response.value 轉成 jsonArray ,再將 jsonArray 內的物件用 Site 包起來並 append 進去 Sites

var sites = [Site]()

let jsonArray = JSON(value)["result"] ["results"].array
jsonArray.forEach({ (json) in
                    let site = Site(json: json)
                    sites.append(site)
                })

Step 3. - Storyboard : MapView and DetailView

接收到 Server 所回傳的 response.value 後,便要開始設計該如何呈現畫面。 TaipeiAPI 主要有兩個畫面:

  • 以地圖方式呈現施工地點
  • 顯示施工地點的詳細資訊

於是,便在Storyboard分別拉了MapViewController與DetailViewController,並在MapViewController前設置了NavigationController。MapViewController 上方的 Label 會顯示施工地點的數量,中間的 MapView 則會顯示每個施工地點的位置,下方的三個 Label分別會顯示「行政區」、「與目前位置的距離」及「預估到達時間」;而 DetailViewController 則會顯示施工地點的詳細資訊

當然,每個 Component 之間都設定好了 Autolayout ,以對應不同尺寸的 iPhone 螢幕,DetailViewController 的地方是將所有Component 用 StackView 包起來,以方便統一調整

Step 4. - Delegate - MapViewDelegate

設計完畫面之後,就要開始實作 MKMapViewDelegate 下的 Function,由於 APP 一開始就要抓取到使用者的目前為止,因此就需要實作 didUpdateUserLocation

mapView(mapView: MKMapView, didUpdateUserLocation userLocation: MKUserLocation) {
        ...
}

接下來是要在地圖上將施工地點用 Annotation 的方式呈現,於是先在 viewForAnnotation 內定義 Annotation 的樣式

mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        ...
}

Note: AnnotationView 的顯示方式就如同 TableViewCell 一樣加了 Reuse 的機制,Reuse 機制是為了做到顯示和資料分離,來達到既不影響顯示效果,又能充分節約資源的目的。

接著再用 For 迴圈將所有的施工地點以 Annotation 的方式加到地圖上

        for i in 0...result.count-1 {
            let location = CLLocationCoordinate2DMake(lat, long)
            let information = MKPointAnnotation()
            information.coordinate = location
            information.title = result[i].cityName
            information.subtitle = result[i].address

            mapView.addAnnotation(information)
        }

而在選取地圖上的任意一個 Annotation 時,APP 就會計算出所選的 Annotation 位置與目前位置之間的距離與預估開車會到達的時間,因此就要在 didSelectAnnotationView 實作這個部分

mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
    let request = MKDirectionsRequest()
        .
        .
        .
    let directions = MKDirections(request: request)
        directions.calculateETAWithCompletionHandler { response, error in
            if let error = error {
                    ...
            } else {
                    ...
            }
        }
    }

最後要實作的部分是 calloutAccessoryControlTapped 。在點擊 Annotation 時,會有一個可進入詳細資訊畫面的 Button ,因此在先前所提到的 viewForAnnotation 底下新增一個 DetailButton

let detailButton = UIButton(type: .DetailDisclosure)
        .
        .
        .
annotationView?.rightCalloutAccessoryView = detailButton

而 DetailButton 的功能則是定義在 calloutAccessoryControlTapped ,在這邊決定當 DetailButton 被點擊時,會執行 performSegueWithIdentifier

mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
    performSegueWithIdentifier("SegueIdentifier", sender: self)
        }

Step 5. Segue - PrepareForSegue

當 DetailButton 被點擊時,會執行 performSegueWithIdentifier 進入到詳細資訊的畫面,因此就需要將 Storyboard 上的兩個畫面利用 Segue 連結起來並設定好 Identifier

設定好 Identifier 之後,需要透過 prepareForSegue 來幫忙傳遞詳細資訊畫面上所要呈現的東西

prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "showDetail" {
            let destinationVC = segue.destinationViewController 
                    .
                    .
                    .
        }
    }

如此一來,TaipeiAPI 就大功告成了!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors