#22 製作臺灣迷因測驗 App
Delegate data transfer / Local JSON parsing / Table View / UserDefaults / UIView & NSLayoutConstraint animation
delegate 傳資料架構
情境:
當 ViewDidLoad 中的 main thread 想透過 QuestionModel 的方法 getQuestions() 抓取遠端 JSON 資料,為了不讓 main thread 因等待資料送回而凍結 UI,我們不讓方法直接 return,而是透過 protocol 和 delegate 實現「通知 main thread 資料已送達並回傳」的目的。
如此一來,main thread 就能直接回到 ViewController 處理其他 UI 事件,抓 JSON 資料的工作交給 background thread 即可。
步驟:
- 發明協定和其方法。
- 讓 ViewController 遵從協定。
- 在 QuestionModel 內宣告
delegate
屬性,delegate 會遵從協定。 - 在 ViewController 生 QuestionModel 物件
model
。ViewDidLoad 內寫model.delegate = self
。 - 定義該協定的方法。
解說:
ViewDidLoad 內,model 呼叫 getQuestions() 方法。
getQuestions() 方法內,delegate 呼叫協定的 questionsRetrieved 方法,以欲傳資料為參數。即可透過 questionsRetrieved 定義的內容把資料送給 ViewController 的屬性存放。
Model
QuestionData.json
Question.swift
Question 內容要與 QuestionData.json 內的資料名稱完全相同。Question 遵從 Codable protocol,就可以把 json 檔 map 到 Question。
QuizModel.swift
QuizModel 內設置 delegate property、getQuestions() 方法,getQuestions() 內呼叫的 getLocalJsonFile() 會解析本地 JSON。
QuizProtocol
View
首頁點按鈕轉場、View 圓角、畫漸層背景、拉 IBOutlet 等細節就不提了。可參考我的文章:
Table View Cell 內 label 四邊都有 AutoLayout constraint,此時 Xcode 不知道我們的 cell 高度是隨內容彈性變動的,所以發出 label 文字可能被 clip 的黃色警告,可忽略。
Cell 的 Identifier 設為 “ChoiceCell”,Label 的 View 的 Tag 設為 1,cellForRowAt 方法會用到。
Cell 的 Selection 設為 None,點選時才不會有顏色跑出來。
Controller
ViewController class
宣告並初始化會用到的物件和變數。
Delegate 傳資料實現
步驟:
- 發明 QuizProtocol 和其方法。
- ViewController conforms to QuizProtocol.
3. QuizModel class 宣告 delegate property,delegate 會遵從協定。
4. 設定 ViewController 為 QuizModel 物件 model 的 delegate。
5. 定義 questionsRetrieved(_ questions:[Question])
方法。
解說:
ViewDidLoad 內,model 呼叫 getQuestions() 方法。
getQuestions() 方法內,delegate 呼叫協定的 questionsRetrieved 方法,以欲傳資料為參數。即可按照 questionsRetrieved 定義的內容把資料送給 ViewController 的屬性存放。
questionsRetrieved 方法裡面,又呼叫了顯示問題方法 displayQuestion(),它可以:
(1)顯示問題圖片、名稱。(2)用 reloadData() 方法顯示 tableView。
Table View 實現
- ViewController 遵從 UITableViewDelegate, UITableViewDataSource。
2. 設定 ViewController 為 tableView 的 datasource(與顯示方法有關)和 delegate(與互動方法有關)
tableView.delegate = self
tableView.dataSource = self
3–1. 定義 UITableViewDatasource Methods:
・numberOfRowsInSection / cellForRowAt
3–2. 定義 UITableViewDelegate Methods:
・didSelectRowAt
傳資料與 present 前置作業(承上)
・ViewController 的 property:
var resultDialogVC: ResultViewController?
・viewDidLoad 內:
resultDialogVC = storyboard?.instantiateViewController(identifier: "ResultVC") as? ResultViewControllerresultDialogVC?.modalPresentationStyle = .overCurrentContext
・ResultViewController 內:
var resultTitleText: String!
var feedbackText: String!
var buttonText: String!
在 ResultViewController 點選按鈕,再換頁(跳到總結頁/第一頁/下一頁)
步驟
- 發明 ResultViewControllerProtocol 協定和其方法 dialogDismissed()。
- ViewController 遵從 ResultViewControllerProtocol。
- ResultViewController 內宣告 delegate property,delegate 會遵從協定。
var delegate: ResultViewControllerProtocol?
4. 將 ResultViewController 之 resultDialogVC 物件的 delegate 設為 ViewController:
resultDialogVC?.delegate = self
5. 呼叫 dialogDismissed 方法:
寫在 ResultViewController 的按鈕觸發方法 nextTapped 內。
淡出元件後,先用 dismiss 方法把 presented modally 的 view controller(在此為 ResultViewController)關掉。
接著 ResultViewController 的 delegate property 呼叫 dialogDismissed 方法。
6. 定義 dialogDismissed() 方法。
用 UserDefaults 保存測驗狀態
保存狀態值方法、取得已儲存狀態值方法、清除已儲存狀態值方法
import Foundationclass StateManager {
static var numCorrectKey = "NumberCorrectKey"
static var questionIndexKey = "QuestionIndexKey" // 保存狀態值方法
static func saveState(numCorrect: Int, questionIndex: Int) {
let defaults = UserDefaults.standard
defaults.set(numCorrect, forKey: numCorrectKey)
defaults.set(questionIndex, forKey: questionIndexKey)
} // 取得已儲存狀態值的方法
static func retrieveValue(key: String) -> Any? {
let defaults = UserDefaults.standard
return defaults.value(forKey: key)
} // 清除已儲存狀態值的方法
static func clearState() {
let defaults = UserDefaults.standard
defaults.removeObject(forKey: numCorrectKey)
defaults.removeObject(forKey: questionIndexKey)
}
}
dialogDismissed 方法內,顯示問題後,要保存測驗狀態
dialogDismissed 方法內,顯示總結對話窗後,要清除測驗狀態
questionsRetrieved 方法內,顯示問題之前,要回復上次測驗狀態
淡入、淡出動畫/滑入、滑出動畫
dimView / resultTitleLabel / feedbackTextView 淡入
dimView 淡出
題目畫面滑入動畫
題目滑入方法、題目滑出方法
定義:
呼叫:
顯示問題方法最後面,加入題目滑入方法。
didSelectRowAt 方法的傳資料動作之前,加入題目滑出方法。
假設有個遠端 JSON 檔格式也相同
用以下函式抓題目,getQuestions() 內也呼叫此函式。