顯示廣告
隱藏 ✕
看板 KnucklesNote
作者 Knuckles (站長 那克斯)
標題 [Xcode][Swift3] 使用 Alamofire 存取網站資料
時間 2017-03-07 Tue. 18:06:38


延續上一篇 [Xcode][Swift3] 使用 Table View 產生列表頁 - KnucklesNote板 - Disp BBS
接下來要抓網路上的資料來顯示

要用 Swift 存取網路資料,有個好用的第三方類別庫 Alamofire
是與 Object-C 的 AFNetworking 同一個作者建立的
[圖]

參考 GitHub: https://github.com/Alamofire/Alamofire

依照這篇 [Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS
使用 CocoaPods 安裝 Alamofire 後
為了要顯示網路上的圖檔,還要再安裝 AlamofireImage
參考 GitHub: https://github.com/Alamofire/AlamofireImage

修改專案資料夾下的 Podfile
在 pod 'Alamofire' 下再加上一行
  pod 'AlamofireImage'

使用終端機,切換到專案資料夾執行 pod install
$ cd ~/Xcode/DispBBS
$ pod install

目前安裝的版本為
Alamofire 4.4.0
AlamofireImage 3.2.0



回到 Xcode

修改 HotTextViewController.swift
在 import UIKit 下加上
import Alamofire
import AlamofireImage

如果出現錯誤訊息「cannot load underlying module for 'Alamofire'
[圖]

點一下「Product」/「Clean」就可以了
[圖]



取消只能用加密連線的限制

在 Xcode 7 之後的版本,預設網路存取要使用加密連線 (https://) 才行
否則會無法連線,並出現錯誤訊息「App transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.」

要取消限制,到專案設定的「info」,在「Custom iOS Target Properties」項目裡
點一下任一項目後面的✚
[圖]


輸入「App Transport Security Settings」,按 Enter
[圖]


點一下左邊的向右箭頭,變成下向箭頭後,點右邊的✚
[圖]


選擇「Allow Arbitrary Loads」,按 Tab,選擇 Value 為「YES」
[圖]



熱門文章的 JSON 檔格式

先看一下我們要抓的 JSON檔 http://disp.cc/api/hot_text.json
這是由 Server 端產生的 API,裡面的資料格式是由網頁後端程式(例如PHP)來產生的
格式為每個網站自訂的,所以要抓其他網站的資料時要再修改一下

可以用 Chrome 的網站管理工具將 JSON 檔用樹狀結構顯示出來看看
[圖]


這個熱門文章列表的 JSON 格式是像這樣
{"isSuccess":1,"err":0,"totalNum":28,"list":[Row1, Row2, ..., RowN] }
最外層是一個JS物件{"key":value},
"isSuccess" 用來檢查資料有沒有產生成功,沒問題的話就是 1
"totalNum" 總共有幾篇熱門文章
"list" 為一個JS陣列[value],裡面存了N個JS物件,記錄了每篇熱門文章的資料
       每個JS物件的格式是像這樣
       Row1 = {"hot_num":"目前人氣值","bi":"看板編號","ti":"文章編號","title":"文章標題","board_name":"看板名稱","ai":"作者編號","author":"作者帳號","desc":"文章摘要","img_list":["縮圖網址1","縮圖網址2","縮圖網址3"], "url":"文章網址"}

在 Swift 裡,要將 JS物件 存成 Dictionary
將 JS陣件 存成 Array


使用 Alamofire 讀取網站的 json 檔

修改 HotTextViewController.swift

在 class HotTextViewController: UITableViewController { 這行下面加上
    // 1. 新增成員變數
    var hotTextArray:[Any]?

    // 2. 新增成員函數
    func loadData() {
        let urlString = "https://disp.cc/api/hot_text.json"
        // 3. 使用 Alamofire 存取網址
        Alamofire.request(urlString).responseJSON { response in
            if let JSON = response.result.value {
                // 4.
                print("JSON: \(JSON)")
            }
        }
    }
1. 新增一個成員變數 hotTextArray,型別為[Any],代表任意物件的陣列
   型別後面加上問號代表是一個可以為 nil 值的 optional 變數
   用來儲存列表的資料

2. 新增一個成員函數 loadData(),用來讀取網路上的 json 檔
   使用 let urlString 代表宣告一個常數,urlString 的值之後不能再變動

3. 當 Alamofire.request() 執行完後,會使用非同步的方法執行 responseJSON()
   responseJSON 傳入了一個 callback 匿名函數:{ 輸入值 in 函數內容 }
   也就是 responseJSON({匿名函數}),
   當參數只有傳入一個函數時可簡寫為:resposnseJSON {匿名函數}

4. 先將讀取到的 JSON 資料顯示在 Console 視窗,看看有沒有讀取成功

然後在成員函數 viewDidLoad() 裡加上
        loadData()
viewDidLoad 是 view 載入後會執行的函數
在這邊呼叫 loadData() 載入資料

執行結果
[圖]

點程式碼左下角的按鈕可顯示下方的 Debug Area
點 Debug Area 右下角的按鈕可將左邊的 Variable View 隱藏

在 Console 視窗可看到用 print() 指令顯示的結果
可以確定網站的 json 檔有成功的讀取到了

修改成員函數 loadData() 將列表的資料存到成員變數 hotTextArray
    func loadData() {
        let urlString = "https://disp.cc/api/hot_text.json"
        Alamofire.request(urlString).responseJSON { response in
            // 1.
            guard response.result.isSuccess else {
                let errorMessage = response.result.error?.localizedDescription
                print(errorMessage!)
                return
            }
            guard let JSON = response.result.value as? [String: Any] else {
                print("JSON formate error")
                return
            }
            //print("JSON: \(JSON)")
            // 2.
            if let list = JSON["list"] as? [Any] {
                self.hotTextArray = list
                self.tableView.reloadData()
            }
        }
    }
1. 使用 guard 檢查網路存取是否成功,若失敗的話使用 return 跳出
   guard 條件 else { //條件失敗要做的事 }
   等同於 if 條件 == nil { //條件失敗要做的事 }

   第二個 guard 將 response.result.value 轉型為 [String: Any] 的 Dictionary,
   轉型的 as? 後面加問號代表傳型失敗就直接傳回 nil 值
   轉型後的結果存為常數 JSON

2. 讀取 JSON["list"],轉型為 [Any] 陣列,存為常數 list
   成功的話將 list 存到成員變數 hotTextArray

   有變更資料就要執行一次 reloadData(),更新 tableView 顯示的資料


修改成員函數 tableView(_:numberOfRowsInSection)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if let num = self.hotTextArray?.count {
        return num
    } else {
        return 0
    }
}
依照陣列儲存的數目來顯示 tableView 的列數
因為 self.hotTextArray?.count 可能會取得 nil 值
所以不能直接 return self.hotTextArray?.count


修改成員函數 tableView(_:cellForRowAt)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "HotTextCell", for: indexPath) as! HotTextCell

    // 1. 讀取第 indexPath.row 的值
    guard let hotText = self.hotTextArray?[indexPath.row] as? [String: Any] else {
        print("Get row \(indexPath.row) error")
        return cell
    }

    // 2. 填入 Label 的值
    cell.titleLabel?.text = hotText["title"] as? String
    cell.descLabel?.text = hotText["desc"] as? String

    // 3. 顯示縮圖
    let img_list = hotText["img_list"] as? [String]
    let placeholderImage = UIImage(named: "displogo120")
    if img_list?.count != 0 {
        let url = URL(string: (img_list?[0])!)!
        cell.thumbImageView?.af_setImage(withURL: url, placeholderImage: placeholderImage)
    } else {
        cell.thumbImageView?.image = placeholderImage
    }

    return cell
}
1. 讀取成員變數 hotTextArray 的第 indexPath.row 個值,
   存成型態為 [String: Any] 的 Dictionary 變數 hotText

2. 讀取 hotText 中的值時,例如 hotText?["title"]
   要加上 as? String 將型態由 Any 轉為 String

3. hotText 中的縮圖 img_list 是一個存有 0~3 個圖片網址的陣列
   所以將型態由 Any 轉為 [String] 後存在常數 img_list

   用 if 檢查若縮圖的數目不為 0,將第一個縮圖顯示出來
   對 UIImageView 使用 AlamofireImage 提供的 af_setImage(withURL:placeholderImage:)
   輸入圖片網址 url 與載入圖片前要先顯示的圖 placeholderImage

   若縮圖數目為0,則使用之前存在專案裡的 displogo120 來顯示


執行結果
[圖]



存取網路時顯示載入中的圖示

要讓 Alamofire 在存取網路時自動在左上方狀態列顯示載入中圖示
[圖]


要再另外安裝 AlamofireNetworkActivityIndicator
GitHub: https://github.com/Alamofire/AlamofireNetworkActivityIndicator

修改 Podfile 加上
  pod 'AlamofireNetworkActivityIndicator'

使用終端機在專案目錄執行
$ pod install

目前安裝的版本為 AlamofireNetworkActivityIndicator 2.1.0


修改 AppDelegate.swift

在 import UIKit 下一行加上
import AlamofireNetworkActivityIndicator

在第一個成員函數 application(_:didFinishLaunchingWithOption:)
的 return true 之前加上
        NetworkActivityIndicatorManager.shared.isEnabled = true
        NetworkActivityIndicatorManager.shared.startDelay = 0
這樣使用 Alamofire 存取網路時就會自動顯示載入中圖示了

startDelay 預設值是 1,代表網路開始存取後過一秒還在存取的話才顯示
若是想要網路開始存取時就馬上顯示的話可以改成 0


如果不是使用 Alamofire 時,也想顯示載入中圖示的話
可以使用內建的
        UIApplication.shared.isNetworkActivityIndicatorVisible = true

要關閉時使用
        UIApplication.shared.isNetworkActivityIndicatorVisible = false


加入重整按鈕與下拉重整功能

修改 HotTextViewController.swift
新增一個成員函數 refresh(_:)
    @IBAction func refresh(_ sender: Any) {
        loadData()
    }
@IBAction 是用來連結按鈕點擊動作與成員函數用的

在 storyboard 拉一個 Bar Button Item 到 Navigation Bar 右邊
在屬性設定選擇 System Item: Refresh
[圖]


在 Table View Controller 的連結設定
將 Received Actions 裡的 refresh: 右邊的圈圈拉到重整按鈕上
[圖]


接著開啟 Table View 原本就內建的下拉重整功能 (pull-to-refresh)
在 Table View Controller 的屬性設定
將 Refreshing 設定為「Enabled」
下方 Title 設定置中,文字輸入「更新熱門文章」
[圖]



修改 HotTextViewController.swift

在成員函數 viewDidLoad() 裡加上一行
        self.refreshControl?.addTarget(self, action: #selector(refresh(_:)), for: UIControlEvents.valueChanged)
將 refreshControl 要執行的動作選擇我們剛剛加上的成員函數 refresh(_:)

然後修改載入資料的函數 loadData()
在 Alamofire.request(urlString).responseJSON { response in 的下一行加上
            self.refreshControl?.endRefreshing()
在下載完資料時使用 endRefreshing() 關閉載入中的圖示


執行看看

將列表往下拉時,就會出現「更新熱門文章」與載入中的圖示
放開後等載入完成就會關閉圖示
[圖]



要在點擊列表之後,將取得的文章網址在另一個頁面顯示出來,請看下一篇:
[Xcode][Swift3] 點擊列表開啟並傳送資料至新的頁面 - KnucklesNote板 - Disp BBS


參考
AppCoda Swift 網路程式設計指南:如何使用 Alamofire
RayWenderlich Alamofire Tutorial: Getting Started
Basics of Pull to Refresh for Swift Developers

--
※ 作者: Knuckles 時間: 2017-03-07 18:06:38
※ 編輯: Knuckles 時間: 2017-03-29 22:14:59
※ 看板: KnucklesNote 文章推薦值: 0 目前人氣: 0 累積人氣: 1449 
分享網址: 複製 已複製
r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇