LoginSignup
4
8

More than 5 years have passed since last update.

【Swift】NotificationCenterのObserverとUserInfoの型を紐付ける

Posted at

はじめに

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 InfoTypestatic 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:)で返されるNoticeObserverdispose()を呼ぶことで解除が可能です。

カスタマイズ

Notificationを取得

おおもとのNotificationも取得したい場合は、observe時のrecievingにクロージャーを渡すことで取得が可能です。

UIKeyboardWillShow.observe(recieving: { notification in
    print(notification)
}) { keyboardInfo in
    print(keyboardInfo)
}.addObserverTo(pool)

任意のキーで値を直接取得

先程定義したUIKeyboardWillShowUIKeyboardInfoを使わずに直接値を取りたい場合などは、下記のように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の通知監視の解除方法が面白いなと思っていたので、そちらを参考にさせていただいています。

4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8