はじめに
NotificationCenterを利用しようとした際に、userInfoに入っている値を取得したりキャストしたりするのが煩わしく感じることはないでしょうか?
通知登録時に受け取りたい値の型も明記することなく、直接値を利用できる方法を紹介していきたいと思います。
NotificationCenterの利用例
例としてキーボードが立ち上がった際に、画面に表示されているUITableViewのheightを変更する処理を記述します。
NotificationCenterを使用してNotificationを監視する際に、下記のコードのようにnotificationのuserInfoにキーを指定してオブジェクトを取得し、そのオブジェクトを任意の型にキャストすることになると思います。
NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: .main) { [unowned self] notification in
guard
let userInfo = notification.userInfo,
let keyboardInfo = UIKeyboardInfo(info: userInfo)
else { return }
self.tableViewBottomConstraint.constant = keyboardInfo.frame.size.height
UIView.animate(withDuration: keyboardInfo.duration, delay: 0, options: keyboardInfo.animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
上記のコードで利用しているUIKeyboardInfo
は、Dictionaryの中から必要な値を取得し自身のPropertyとして保持する実装になっています。
struct UIKeyboardInfo {
let frame: CGRect
let animationDuration: TimeInterval
let animationCurve: UIViewAnimationOptions
init?(info: [AnyHashable : Any]) {
guard
let frame = (info[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let duration = info[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval,
let curve = info[UIKeyboardAnimationCurveUserInfoKey] as? UInt
else {
return nil
}
self.frame = frame
self.animationDuration = duration
self.animationCurve = UIViewAnimationOptions(rawValue: curve)
}
}
NoticeObserveKitを利用することで、任意のオブジェクトを直接受け取れるようになりつつ、Observerに対して特定の型を紐付けることができるので、キャストをしたり型を明記する必要がなくなります。
UIKeyboardWillShow.observe { [unowned self] keyboardInfo in
self.tableViewBottomConstraint.constant = keyboardInfo.frame.size.height
UIView.animate(withDuration: keyboardInfo.duration, delay: 0, options: keyboardInfo.animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
NoticeObserveKitの利用方法
CocoaPods
またはCarthage
からインストールすることができます。
pod "NoticeObserveKit" #CocoaPods
github "marty-suzuki/NoticeObserveKit" #Carthage
まず、NoticeType
を採用したstruct
を定義します。
NoticeType
によって、typealias InfoType
とstatic let name: Notification.Name
の実装が強制されているので、それらを実装します。
InfoType
に定義するものは、実際に受け取りたいオブジェクトの型になります。
struct UIKeyboardWillShow: NoticeType {
typealias InfoType = UIKeyboardInfo
static let name: Notification.Name = .UIKeyboardWillShow
}
上記で定義したstructのobserve(queue:object:recieving:using:)
を呼ぶだけで、通知監視が完了します。
ViewControllerでの利用例
実際にViewController内で利用する際には、下記のような実装になります。
import UIKit
import NoticeObserveKit
class ViewController: UIViewController {
private let searchBar = UISearchBar(frame: .zero)
private var pool = NoticeObserverPool()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
navigationItem.titleView = searchBar
configureObservers()
}
private func configureObservers() {
UIKeyboardWillShow.observe { [unowned self] in
print("UIKeyboard will show = \($0)")
}.addObserverTo(pool)
UIKeyboardWillHide.observe { [unowned self] in
print("UIKeyboard will hide = \($0)")
}.addObserverTo(pool)
}
}
NoticeObserverPool
にobserverを登録しておくことで、ViewControllerが破棄されたタイミングで通知監視も解除されるようになります。
それ以外の任意のタイミングで通知監視を解除したい場合は、observe(queue:object:recieving:using:)
で返されるNoticeObserver
のdispose()
を呼ぶことで解除が可能です。
カスタマイズ
Notificationを取得
おおもとのNotification
も取得したい場合は、observe時のrecieving
にクロージャーを渡すことで取得が可能です。
UIKeyboardWillShow.observe(recieving: { notification in
print(notification)
}) { keyboardInfo in
print(keyboardInfo)
}.addObserverTo(pool)
任意のキーで値を直接取得
先程定義したUIKeyboardWillShow
をUIKeyboardInfo
を使わずに直接値を取りたい場合などは、下記のようにstatic let infoKey: String
に取得したい値のキーを定義することが実現できます。
struct UIKeyboardWillShow: NoticeType {
typealias InfoType = NSValue
static let infoKey: String = UIKeyboardFrameEndUserInfoKey
static let name: Notification.Name = .UIKeyboardWillShow
}
自前のNotifiation
例として、UINavigationControllerでpushしたViewControllerが表示されたことをpostする実装をしていきます。実際に利用する際は、下記のようになるかと思います。
extension ViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
let content = NavigationControllerContent(viewController: viewController, animated: animated)
NavigationControllerDidShow.post(info: content)
}
}
ここで定義するNoticeType
を採用したstructは下記のようになります。
struct NavigationControllerDidShow: NoticeType {
typealias InfoType = NavigationControllerContent
static var name = Notification.Name("navigationControllerDidShow")
}
最後に
NotifwiftでNSNotification.userInfoをSwiftyに扱おう - Qiitaで紹介されている、Notifwiftの通知監視の解除方法が面白いなと思っていたので、そちらを参考にさせていただいています。