10.%d Xcode Study — Passing Data using UserDefaults & Notification
目錄
⦿ 回顧轉場與傳值
⦿ UserDefaults傳值
⦿ NotificationCenter傳值
⦿ 反向傳值
⦿ 正向傳值
回顧轉場與傳值
在上一篇中,我們說了 Segue 的用法,可以使用控件到 VC,也可以使用 VC 到 VC,正向的轉場我們叫做 Segue,反向的轉場叫做 Unwind Segue,他們都可以使用 prepare
來傳值,只要你搞清楚 destination 跟 source;也可以用 IBSegueAction
來傳值。
更重要的,你得弄懂 show 跟 present 的差異,在有使用 navigationController 的情況下,show 就像 present Modally
。
也要知道什麼時候 VC 生成,並且可以讀取它的 property 並達到傳值的目的,如下:
@IBAction func aEntered(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let bvc = storyboard.instantiateViewController(withIdentifier: "bViewControllerID") as! BViewController
self.present(bvc, animated: true, completion: nil)
bvc.bLabel.text = aTextField.text!
}
必須在 VC present(需注意生命週期
)出來後才能將值傳遞過去,倘若進到 navigationController 的堆疊
中則可以先傳再 push。
當然,如果你要用輸入框的 text 來傳值,我們 VC 可以 conform UITextFieldDelegate,並加上下面這段程式碼:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
}
在你按下 return 後能夠將鍵盤返回。結果如下:
不過除非你很確定控件
的存取,不會在因為還沒生成時 unwrapping nil 造成程式閃退,我們還是盡量使用 property 來傳遞資訊。
繼續閱讀|回目錄
UserDefaults傳值
在 Xcode 中提供了 UserDefaults 這個 API 讓開發者使用,UserDefaults 牽涉到的是 Data Persistance(資料持久化保存)的 Issue。
在應用程式中常會有一個 preference 的選項,即是使用者偏好設定
,我們希望應用程式會記錄這個偏好設定,而不是每次打開都需要去設定一次。
所以,UserDefaults 就適合用來保存一些不是那麼機密的資料,並且,它是一個輕量化的儲存介面,以 XML 格式去儲存檔案,而它的真實形貌就是 plist
了,不過這個 plist 並非 info.plist,要找到 UserDefaults 這個 plist 檔案,請參閱下面這篇文章。
接著,我們要來實際使用 UserDefaults,在之前的文章有提到用全域變數來傳值,全域變數的壞處是任何地方都可以存取,如果這個變數在這邊儲存,又在那邊修改,最後會變得難以追蹤。
當然並不是說 UserDefaults 就適合用來取代全域變數,是為了引出不同概念的儲存,全域變數在 APP 關閉後記憶體就釋放了,而 UserDefaults 則是以檔案的形式被保存了下來,當然,如同前面所說,這個檔案你不是那麼容易找到並打開它,所以很適合用來做 Preference。
我們來看看程式碼:
A 頁@IBAction func aEntered(_ sender: Any) {
let aSentence = aTextField.text!
UserDefaults().setValue(aSentence, forKey: "aEntered")
print("\(UserDefaults().string(forKey: "aEntered") ?? "none")")
}
我們在 A 頁創建一個 UserDefaults 的實例,並 setValue 給一個特定的 key,而這個 value 是 A 頁控件
的值。
B 頁override func viewDidLoad() {
super.viewDidLoad()
bTextField.delegate = self
bLabel.text = "\(UserDefaults().string(forKey: "aEntered") ?? "none")"
}
在 B 頁將控件
設定為 UserDefaults 的值,從當初設定的 key 中取出來。
繼續閱讀|回目錄
Notification傳值
NotificationCenter 是 Xcode 中提供一個監聽的方法,在之前的文章就是用 NotificationCenter 來監聽鍵盤彈出的位置,比對是否擋到輸入框,來判斷該頁的 View 需要提高多少,參考如下:
反向傳值
在 B 頁回到 A 頁的傳值(反向傳值)中,B 頁的程式碼如下:
我們從 .default 去加入監聽,並為這個監聽寫了一個 objc 的方法,監聽到事件則觸發這個方法。
在觸發方法裡,如果你發送通知的是物件,我們就透過 .object 去取到它,而 object 是 Any?
,所以它可以是 UITextField,也可以是 UITextField.text,端看你如何去 post。
name 則是這個監聽事件的名稱,我們可以加入下面的程式碼:
extension Notification.Name {
static let myObserver = Notification.Name("myObserver")
}
這樣 name 就可透過 .myObserver
去讀到,不用麻煩地每次放入參數都要 Notification.Name(“myObserver”)
了。
A 頁的程式碼如下:
如前所述,利用了post,這邊的物件是 UITextField.text,所以前面的 notification 才要轉型為 String。
我們看看這個 post,如下:
在 Discussion 的地方告訴我們,你可以使用 userInfo 或者使用 object,userInfo 是 key-value 形式的 Dictionary
,object 則是 Any?
。
下面再展示一下反向傳值的結果吧!
正向傳值
正向傳值的地方比較麻煩,我們先貼上程式碼,在 A 頁面:
@IBAction func aBtnPressed(_ sender: Any) {
performSegue(withIdentifier: "fromAtoB", sender: nil)
guard let text = aTextField.text else { return }
NotificationCenter.default.post(name: .aceObserver,
object: nil,
userInfo: ["text": text])
在 B 頁面:
required init?(coder: NSCoder) {
print("===> BViewController init")
super.init(coder: coder)
// MARK: - 設定監聽
NotificationCenter.default.addObserver(self,
selector: #selector(received),
name: .aceObserver,
object: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
bLabel.text = name
}
@objc func received(sender: Notification) {
let text = sender.userInfo?["text"] as? String
name = text!
}
這裡的重點仍在於 VC 及控件的生命週期
,我們必須要確保 B 頁面已經註冊通知,所以在 A 頁面先 performSegue,而既然使用 performSegue,就代表 B 是在 required init?(Storyboard) 中生成的,接著在 A post 通知,B 就會觸發 received,藉由 received 去更改 B 的 property,因為這時 UILabel 尚未形成,最後,我們在 viewDidLoad 中去設定 UILabel。
是不是有點複雜呢?我們再複習一次。
A performSegue 在 B required init? 時設定監聽,A 再 post userInfo,這時 B received 去改變自己的 property,最後在 viewDidLoad 中用 property 設定 bLabel。
這次就分享到這,感謝您的閱讀。
繼續閱讀|回目錄
Reference: