#24 製作臺灣頭條新聞 App(串接 News API)

Delegate data transfer / Remote JSON parsing / Simple image caching / segue or performSegue(withIdentifier:sender:) + prepare(for:sender:) data transfer / WKNavigationDelegate / UIRefreshControl

Ethan
彼得潘的 Swift iOS / Flutter App 開發教室
11 min readAug 14, 2021

--

Demo

delegate 傳資料

概念同 #22 製作臺灣迷因測驗 App,要透過 protocol 和 delegate 實現「通知 main thread 資料已送達並回傳」的功能。

步驟

・發明 ArticleModelProtocol 協定和其方法 func articlesRetrieved(_ articles:[Article])

・讓 ViewController 遵從 ArticleModelProtocol 協定。

・在 ArticleModel 內宣告 delegate,delegate 會遵從協定。

・在 ViewController 生 ArticleModel 物件 model。ViewDidLoad 內寫 model.delegate = self

・定義該協定的方法

解說

・ViewDidLoad 內,model 呼叫 getArticles 方法。

・getArticles 方法內,讓 delegate 執行協定的 articlesRetrieved 方法以欲傳資料為參數。

・即可透過 articlesRetrieved 方法把資料送給 ViewController 的 articles 屬性

補完 getArticles 函式,從 API 抓資料

View

加入 tableView / ArticleCell,DetailViewController / webView

・Cell 的 Identifier 設為 “ArticleCell”。…cellForRowAt… 方法內會用到。

・ArticleCell 和 Table View 的 Row Height 都設為 100。

・ArticleCell 的 Selection 選 None,取消點選反白效果。

ArticleCell Row Height = 100 / Table View Cell Row Height = 100

Cell 的 UI 小技巧:

・label 右邊不設 constraint,把 label 寬度設為 ArticleCell 寬度的 0.7 倍再減(label 左右欲留寬度)* 2

・image view 左邊不設 constraint,把 image view 寬度設為 ArticleCell 寬度的 0.3 倍

Controller

建立 tableView 架構

拉 tableView IBOutlet,設定 tableView 的 delegate 和 datasource 是 ViewController

ViewController 遵從 UITableViewDataSource, UITableViewDelegate

return articles.count讓 tableView 知道文章總數。

articlesRetrieved 協定方法最後加入 tableView.reloadData(),如此一來,獲得文章陣列後就會更新 tableView。

目前的畫面 demo,可以看到 cell 筆數(articles.count)是正確的。

客製 Cell 內容

這次的 cell 要抓圖,我們讓 cell 呼叫自己的 method,把 article 傳給 cell 的 property。

新建 ArticleCell 類別(繼承 UITableViewCell)

拉 label, image view 的 IBOutlet 到 ArticleCell

ArticleCell

1. 用 as! 強制轉型成 ArticleCell

2. cell 呼叫 displayArticle 方法,把位於 indexPath.row 位置的 article 傳給 ArticleCell 的 property

ArticleCell

目前畫面 Demo:

在 displayArticle 內開頭先補上 Reset cell 的動作

既然顯示完文字了,緊接著下載圖片、顯示圖片

注意:顯示圖片前,要確認 self.articleToDisplay!.urlToImage 與 urlString 仍相同(可能因 cell recycle 而不同)

還沒滑動前(左);沒判斷式,快速來回滑動後停在下面,出現錯誤(中);有判斷式,快速來回滑動後停在下面,正確(右)

用字典實現簡單 Cache 功能:不用每次都重新下載圖片

import Foundationclass CacheManager {

static var imageDictionary = [String : Data]()

static func saveData(_ url: String, _ imageData: Data) {
// 儲存圖片資料和其 urlString
imageDictionary[url] = imageData
}

static func retrievedData(_ url: String) -> Data? {
// return 圖片資料或 nil
return imageDictionary[url]
}
}

透過 let urlString = articleToDisplay.urlToImage!得到圖片 urlString 後,以該 urlString 從 CacheManager 抓取非 nil 圖片,顯示並 return:

確定下載到新的圖片資料後,把 urlString 和 data 放進 CacheManager 存起來:

點選 Cell 顯示網頁:Segue / performSegue 兩種轉場,搭配 prepare(for:sender:) 傳資料

轉場方法一:

從 “cell” 拉 Segue 到 DetailViewController。

轉場方法二:

1. 從 “ViewController” 拉 Segue 到 DetailViewController。
2. 給 Segue 一個 “goToDetail” 的 Identifier
3. 加入 performSegue(withIdentifier:sender:)。(於 didSelectRowAt 方法中)

用 prepare function 傳資料

動畫

文章標籤、圖片淡入動畫

func displayArticle(_ article: Article) 中。

・Reset cell 後,設定透明度為 0:

・顯示標籤文字後,透明度漸變成 1:

・從 Cache 載入圖片後,透明度漸變成 1:

・載入新圖片後,透明度漸變成 1:

載入時的 Spinner 旋轉動畫

1. Spinner 放在 Detail ViewController 中間,拉 IBOutlet

2. WebKit 的 WKNavigationDelegate 協定、…didFinish…方法

功用是讓 Web View 通知其 navigationDelegate(這邊是 DetailViewController)已經結束 loading 了。

加入///註解部分,實現載入時的 spinner 旋轉動畫。

重新抓資料的 spinner 動畫

設定 tableView 的 property 為 UIRefreshControl 之物件

拖曳觸發函式

文章陣列屬性設定之後,結束 refreshing

參考資料

作業來源

存參

裡面提到:

WKNavigationDelegate ✓(這篇使用的)

UITextViewDelegate

AVSpeechSynthesizerDelegate

URLSessionDataDelegate

MKMapViewDelegate

改用第三方套件 SDWebImage 下載圖片

SDWebImage 套件

主要先 import SDWebImage, imageView 再呼叫 sd_setImage 相關方法。