web-dev-qa-db-ja.com

Swiftを使用してポインターなしのKVOのオブザーバーを追加する

Objective-Cでは、通常、次のようなものを使用します。

static NSString *kViewTransformChanged = @"view transform changed";
// or
static const void *kViewTransformChanged = &kViewTransformChanged;

[clearContentView addObserver:self
                       forKeyPath:@"transform"
                          options:NSKeyValueObservingOptionNew
                          context:&kViewTransformChanged];

KVOのオブザーバーを追加するために選択できる2つのオーバーロードメソッドがあり、唯一の違いはコンテキスト引数です。

 clearContentView.addObserver(observer: NSObject?, forKeyPath: String?, options: NSKeyValueObservingOptions, context: CMutableVoidPointer)
 clearContentView.addObserver(observer: NSObject?, forKeyPath: String?, options: NSKeyValueObservingOptions, kvoContext: KVOContext)

Swiftポインタを使用しない場合、最初のメソッドを使用するためにポインタを逆参照する方法がわかりません。

2番目のメソッドで使用するために独自のKVOContext定数を作成すると、次のように求められます。

let test:KVOContext = KVOContext.fromVoidContext(context: CMutableVoidPointer)

編集:CMutableVoidPointerとKVOContextの違いは何ですか?誰かが私にそれらの両方をどのように使用するか、そしていつ私が一方を他方で使用するのか例を教えてもらえますか?

編集#2:Appleの開発者がこれをフォーラムに投稿したばかりです:KVOContextは廃止されます;コンテキストとしてグローバル参照を使用することが今の方法です。

23
Justin Moore

Xcode 6ベータ3でKVOContextがなくなったので、次のことができます。次のようにグローバル(つまり、クラスプロパティではない)を定義します。

let myContext = UnsafePointer<()>()

オブザーバーを追加します。

observee.addObserver(observer, forKeyPath: …, options: nil, context: myContext)

オブザーバーで:

override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafePointer<()>) {
    if context == myContext {
        …
    } else {
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}
17
Aderstedt

現在、公式に ドキュメントで推奨 という手法があります。これは、プライベートな可変変数を作成し、そのアドレスを環境。

(Swift 3に更新)

// Set up non-zero-sized storage. We don't intend to mutate this variable,
// but it needs to be `var` so we can pass its address in as UnsafeMutablePointer.
private static var myContext = 0
// NOTE: `static` is not necessary if you want it to be a global variable

observee.addObserver(self, forKeyPath: …, options: [], context: &MyClass.myContext)

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if context == &myContext {
        …
    }
    else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}
53
jtbandes

Swift 4-UITableViewControllerポップオーバーでcontentSizeの変化を観察して、不適切なサイズを修正する

迅速な警告を受けていたため、ブロックベースのKVOに変更するための回答を探していましたが、適切な解決策を見つけるためにかなり多くの異なる回答をまとめました。 Swiftlint警告:

Block Based KVO Violation: Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. (block_based_kvo).

私の使用例は、ビューコントローラーのナビゲーションバーのボタンに接続されたポップオーバーコントローラーを提示し、表示されたらポップオーバーのサイズを変更することでした。ポップオーバー自体は、静的セルを含むUITableViewControllerであり、スタイルポップオーバー付きのストーリーボードセグエを介して表示されました。

ブロックベースのオブザーバーをセットアップするには、ポップオーバーUITableViewController内に次のコードが必要です。

// class level variable to store the statusObserver
private var statusObserver: NSKeyValueObservation?

// Create the observer inside viewWillAppear
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    statusObserver = tableView.observe(\UITableView.contentSize,
        changeHandler: { [ weak self ] (theTableView, _) in self?.popoverPresentationController?.presentedViewController.preferredContentSize = theTableView.contentSize
        })
}

// Don't forget to remove the observer when the popover is dismissed.
override func viewDidDisappear(_ animated: Bool) {
    if let observer = statusObserver {
        observer.invalidate()
        statusObserver = nil
    }

    super.viewDidDisappear(animated)
}

オブザーバーがトリガーされたときに以前の値は必要なかったので、options: [.new, .old]オブザーバーを作成するとき。

5
N.W

Swift 4の更新

ブロックベースのオブザーバー関数にはコンテキストは必要ありません。既存の#keyPath()構文は スマートキーパス に置き換えられ、Swift型安全性を実現しています。

class EventOvserverDemo {
var statusObserver:NSKeyValueObservation?
var objectToObserve:UIView?

func registerAddObserver() -> Void {
    statusObserver = objectToObserve?.observe(\UIView.tag, options: [.new, .old], changeHandler: {[weak self] (player, change) in
        if let tag = change.newValue {
            // observed changed value and do the task here on change.
        }
    })
}

func unregisterObserver() -> Void {
    if let sObserver = statusObserver {
        sObserver.invalidate()
        statusObserver = nil
    }
  }
}
1
kaushal