web-dev-qa-db-fra.com

Ajout d'un observateur pour KVO sans pointeurs avec Swift

En Objective-C, j'utiliserais normalement quelque chose comme ceci:

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

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

J'ai deux méthodes surchargées à choisir pour ajouter un observateur pour KVO, la seule différence étant l'argument contextuel:

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

Swift n'utilisant pas de pointeurs, je ne sais pas trop comment déréférencer un pointeur pour utiliser la première méthode.

Si je crée ma propre constante KVOContext à utiliser avec la deuxième méthode, je finis par la demander:

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

EDIT: Quelle est la différence entre CMutableVoidPointer et KVOContext? Est-ce que quelqu'un peut me donner un exemple comment utiliser les deux et quand je voudrais utiliser l'un sur l'autre?

EDIT # 2: Un développeur Apple vient de publier ceci sur les forums: KVOContext s'en va; Utiliser une référence globale comme contexte est la voie à suivre en ce moment.

23
Justin Moore

Maintenant que KVOContext est parti dans Xcode 6 beta 3, vous pouvez procéder comme suit. Définissez une propriété globale (c'est-à-dire pas une propriété de classe) comme ceci:

let myContext = UnsafePointer<()>()

Ajouter un observateur:

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

Dans l'observateur:

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)
    }
}
16
Aderstedt

Il existe maintenant une technique officielle recommandée dans la documentation , qui consiste à créer une variable mutable private et à utiliser son adresse comme contexte.

(Mis à jour pour Swift 3 le 2017-01-09)

// 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 - observer le changement de taille du contenu sur UITableViewController afin de corriger une taille incorrecte

Je cherchais une solution pour changer de KVO basé sur un bloc parce que je recevais un avertissement rapide et que cela me prenait de rassembler plusieurs réponses différentes pour trouver la bonne solution. Avertissement rapide: 

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

Mon cas d’utilisation consistait à présenter un contrôleur de popover attaché à un bouton de la barre de navigation dans un contrôleur de vue, puis à redimensionner le popover une fois qu’il était affiché - sinon, il serait trop gros et ne correspondrait pas au contenu du popover. Le popover lui-même était un UITableViewController qui contenait des cellules statiques et il était affiché via une séquence Storyboard avec un style popover. 

Pour configurer l'observateur basé sur des blocs, vous avez besoin du code suivant dans votre fenêtre 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)
}

Je n'avais pas besoin de la valeur précédente lorsque l'observateur était déclenché, donc omis le options: [.new, .old] lors de la création de l'observateur. 

0
N.W

Mise à jour pour Swift 4

Le contexte n'est pas requis pour la fonction observateur basée sur des blocs et la syntaxe #keyPath () existante est remplacée par smart keypath pour obtenir la sécurité de type 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
    }
  }
}
0
kaushal