#Task-06 圖形聯想心理測驗 App

練習資料傳遞的技巧,上次做計分版已經有用過從 Button 觸發 Segue 傳遞資料,這次換用程式處發,以及練習更好的定義 init 的寫法

在 Storyboard 製作,使用到的元件跟方法:

  • Label
  • Button
  • Struct
  • for 迴圈
  • switch
  • if/else
  • 定義 init 傳輸資料

完整操作:

心理測驗題目來源

一共有四道題目,每題測不同的面向:你剛陷入愛情的反應、對心儀對象的主動程度、心中存在的愛情幻想、現在的壓力指數,每個答案則代表不同的結果,有興趣可以點進連結玩一下!

⭐️ 建立 UI 畫面,用 CAGradientLayer 製作漸層背景

之前還沒試過做漸層背景,這次就來試看看:

  1. 先宣告一個 gradientLayer
  2. 設定這個 layer 的框架(frame)大小等於 view.bounds,也就是跟畫面一樣大
  3. 設定漸層的顏色,要是 CGColor,可以直接設定 CGColor,也可以從UIColor去轉換
  4. 將漸層的 layer 加到畫面最底層(第0層)
  5. 把寫好的 function 放到 viewDidLoad 中
func initialUI(){    let gradientLayer = CAGradientLayer()    gradientLayer.frame = view.bounds    gradientLayer.colors = [UIColor(red: 1, green: 145/255, blue:
167/255, alpha: 1).cgColor, UIColor(red: 1, green: 251/255,
blue: 167/255, alpha: 1).cgColor]
view.layer.insertSublayer(gradientLayer, at: 0)
}

接著準備好所有畫面,以及留好要擺放的文字顯示位置(背景漸層在執行時才會顯示)

Storyboard畫面
App實際執行畫面

⭐️ 確認好需要的內容後,建立一個 Swift File (視需求決定)

用 Struct 定義 Question 跟 Option 的型別,Question 包含題目跟 Option 陣列,Option 中包含選項跟文字

import Foundationstruct Question{
let question: String
let options: [Option]
}
struct Option{
let option: String
let result: String
}

⭐️ QuizViewController

💡 拉 Outlet 跟宣告 Property

class QuizViewController: UIViewController {@IBOutlet weak var questionLabel: UILabel!
@IBOutlet var optionButton: [UIButton]!
//三個選項的按鈕存成陣列,Segue依序拉到按鈕上
var questionIndex = 0 //用來判定顯示第幾個問題
var chooseOptionIndex = 0 //用來判定選第幾個選項
var choices = [String]() //用來儲存選擇的選項文字
var results = [String]() //用來儲存選項對應的結果文字

💡 建立題目、選項及結果內容

宣告陣列,一共 4 個 Question,每個 Question 會包含 1 個問題,3 個選項,每個選項有 1 個結果說明文字

var questions = [    Question(question: "白色的長條物,可以吃的東西,以上形容你會想到以下哪
個?", options: [
Option(option: "香菸", result: "沈穩的你就算心裡很激動,也不會到處
宣揚自己有喜歡的人了。"),
Option(option: "冰棒", result: "表情、語氣、行動瞬間改變,不用說
明,周圍的人也會馬上知道。"),
Option(option: "麵條", result: "表面上裝作很鎮定,內心早就小鹿亂撞
的偽裝派。") ]),
Question(question: "可以提供人們幫助,會發光的圓形物品,以上形容你會想到
以下哪個?", options: [
Option(option: "太陽", result: "算是主動但走低調路線,直到對方注意
到之前都會默默地接近。"),
Option(option: "燈泡", result: "默默守護型,會一直等待對方發現
你。"),
Option(option: "錢幣", result: "為了愛情可以奉獻一切,為了他可以做
任何事只為了獲得好感。") ]),
Question(question: "隨手可得,但可以讓你聽到、看到你觸摸不到的東西,以上
形容你會想到以下哪個?", options: [
Option(option: "電話", result: "現實派,認為喜歡的對象與想像中的憧
憬角色不是一樣的。"),
Option(option: "電視", result: "對於愛情對象有期待,期待會一場美好
的戀愛。"),
Option(option: "望遠鏡", result: "內心充滿對愛情的幻想。")]),
Question(question: "白色且柔軟,卻用手抓不住的東西,以上形容你會想到以下
哪個?", options: [
Option(option: "奶油慕斯", result: "幾乎無壓力,對於現狀很滿
意。"),
Option(option: "雲朵", result: "積累了很多壓力,對於現實有很多不
滿。"),
Option(option: "肥皂泡沫", result: "看起來沒什麼壓力,其實內心有一
點不滿,只是還可以忍受。")])
]

💡 建立題目跟選項顯示的 function (需放在 viewDidLoad 中才會顯示)

func qna(){//題目文字顯示,依照 questionIndex 的值判斷是第幾題
questionLabel.text = questions[questionIndex].question
//選項文字顯示,依照 questionIndex 的值及按鈕順序判斷要顯示哪個選項 optionButton[0].setTitle(questions[questionIndex].options[0].option, for: .normal)optionButton[1].setTitle(questions[questionIndex].options[1].option, for: .normal)optionButton[2].setTitle(questions[questionIndex].options[2].option, for: .normal)}

💡 建立點擊按鈕時執行動作的 function (3 個按鈕連到同一個 IBAction)

@IBAction func chooseOption(_ sender: UIButton) {//用switch設定每個選項點擊時chooseOptionIndex代表的值,後續用來判斷是選
第幾個選項
switch sender{ case optionButton[0]:
chooseOptionIndex = 0
case optionButton[1]:
chooseOptionIndex = 1
case optionButton[2]:
chooseOptionIndex = 2
default:
print("No option button")
}//點擊按鈕後將選項文字及結果文字加入到陣列中儲存,questionIndex判斷是第幾題,chooseOptionIndex判斷是第幾個選項choices.append(questions[questionIndex].options[chooseOptionIndex].option)
results.append(questions[questionIndex].options[chooseOptionIndex].result)
//如果作答題數questionIndex <= 2,表示只做了1~3題,因此questionIndex +1,繼續進入下一題,顯示題目及選項文字 if questionIndex <= 2 {
questionIndex += 1
qna()
//如果questionIndex = 3,表示已經做完4題,觸發顯示結果的Segue執行

}else if questionIndex == 3{
performSegue(withIdentifier: "showResult", sender: nil)
}}

接下來先跳到 ResultViewController 定義 init 及參數,再回來 QuizViewController 設定要傳到 ResultViewController 的資料

⭐️ ResultViewController

💡 宣告 Property

class ResultViewController: UIViewController {//用來顯示選擇的選項及結果的Label,一樣用陣列的方式,把Segue依序拉到每個Label上@IBOutlet var choiceLabel: [UILabel]!
@IBOutlet var resultLabel: [UILabel]!
//儲存傳輸過來的選項及結果文字的陣列,需要給個範圍,因為各有4個值所以count = 4var choiceArray = [String](repeating: "", count: 4)
var resultArray = [String](repeating: "", count: 4)

💡 定義 init,由參數設定 Property

//需要的參數包含coder,跟用來儲存傳輸過來的資料的property
init?(coder: NSCoder, choiceArray: [String], resultArray: [String]){
//因為有4個選項跟結果,用for迴圈執行
for i in 0...3{
self.choiceArray[i] = choiceArray[i]
self.resultArray[i] = resultArray[i]
}
super.init(coder: coder)}

💡 系統會自動出現error,要你增加 required init,按 Fix 修復即可

目的是為了防止父類別的 initializer 失傳,而 fatalError 是指讓程式閃退,基本上 app 執行都不會呼叫到這個 function,可以不用擔心

有關 required initializer 可以參考這篇文章:

💡 撰寫顯示結果的 function,並放到 viewDidLoad 中執行,才能順利顯示傳輸過來的資料們

func loadResult(){    //分別有4個選項跟結果,用for迴圈執行4次    for i in 0...3{
choiceLabel[i].text = "你的答案是:\(choiceArray[i])"
resultLabel[i].text = resultArray[i]
}}override func viewDidLoad() { super.viewDidLoad() initialUI() loadResult()

⭐️ 回到 QuizViewController 設定建立 ResultViewController 時要傳給他的資料

💡 從 QuizViewController 拉 Segue 到 ResultViewController

💡 從 Segue 拉 IBSegueAction

@IBSegueAction func showResult(_ coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ResultViewController? { //結果頁面的choiceArray的資料由choices陣列提供,resultArray的資料由results陣列提供
return ResultViewController(coder: coder, choiceArray: choices,
resultArray: results)
}

這次的作業就完成啦~

⭐️ 資料傳輸的重點應該是要知道自己要傳的資料是什麼還有型態,傳輸的 controller 跟接收的 controller 設定要一致才能成功

⭐️ 資料傳輸流程整理(以下都符合就可以了,順序不一定要一樣)

  • 從 controller A 拉 Segue 到 controller B 切換畫面
  • controller B 宣告初始資料的 init 跟儲存資料的 property
  • 從 Segue 拉 IBAction function 到 controller A,設定切換到 controller B 時要傳入的資料
  • 在 controller B 的 viewDidLoad 中讀取 property 更新畫面 (可以直接在viewDidLoad 中寫,也可以另外建立 function 再放到 viewDidLoad 中)

— — — —

附上 Github 作業連結

參考資料

作業中的圖片來源

學習 PNG由CHENXIN設計

--

--