collection.addObserver(self, forKeyPath: #keyPath(UICollectionView.contentSize), options: .new, context: nil)
collection.observe(\.contentSize) { (collection, change) in
}
when I use "addObserver" to observe contentSize, it's worked,
but observe(.contentSize) is not work, I don't know why.
Compare with the example here:
class MyObserver: NSObject {
#objc var objectToObserve: MyObjectToObserve
var observation: NSKeyValueObservation?
init(object: MyObjectToObserve) {
objectToObserve = object
super.init()
observation = observe(
\.objectToObserve.myDate,
options: [.old, .new]
) { object, change in
print("myDate changed from: \(change.oldValue!), updated to: \(change.newValue!)")
}
}
}
The observe method returns an "observation" token, and you need to hang on to it. It stops observing if/when it goes out of scope.
Related
Hi all I'm trying to implement KVO on one of the string properties within a Singleton class. I'm currently running into some errors when trying to add an observer and was hoping that someone could tell me what I'm doing wrong.
The below shows my singleton class.
class User: NSObject {
static let currentUser = User()
private override init() {}
var pictureString: String?
}
In a separate ViewController's viewDidLoad, I've tried to add an observer to the pictureString variable like so.
self.addObserver(self, forKeyPath: #keyPath(User.currentUser.pictureString), options: [.old, .new, .initial], context: nil)
the problem I'm dealing with right now is that it is stating that currentUser is not KVC-compliant. I'm currently getting the below error.
addObserver: forKeyPath:#"currentUser.pictureString" options:7 context:0x0] was sent to an object that is not KVC-compliant for the "currentUser" property.'
I'd appreciate any help thanks.
See this answer:
As for now, Swift cannot have observable class properties.
If you really want to use a singleton, you could convert it to a non-static singleton with a normal property, which then also can be KVO'd... something like this:
class UserManager: NSObject {
private override init() {}
static var instance = UserManager()
var currentUser = User()
}
class User: NSObject {
dynamic var pictureString: String?
}
Note the dynamic keyword in the variable declaration -- this is needed to make KVO work in Swift (dynamic dispatch is explained here).
Then in your VC's viewDidLoad you can register for changes with this:
UserManager.instance.addObserver(self, forKeyPath: #keyPath(UserManager.currentUser.pictureString), options: [.old, .new, .initial], context: nil)
class Viewcontroller: UIViewController {
let user = User.currentUser
override viewDidLoad() {
super.viewDidLoad()
addObserver(self, forKeyPath: #keyPath(user.pictureString), options: [.old, .new], context: nil)
}
}
You can declare your singleton and use that for #keyPath. Otherwise, you will raise an exception.
I have a class with a static var where the current online connection status is stored. I want to observe the value of ConnectionManager.online through other classes. I wanted to do this with KVO, but declaring a static variable as dynamic causes an error:
class ConnectionManager: NSObject {
dynamic static var online = false
// adding 'dynamic' declaration causes error:
// "A declaration cannot be both 'final' and 'dynamic'
}
What is a most elegant way of doing this?
Update. This my code for the KVO part:
override func viewDidLoad() {
super.viewDidLoad()
ConnectionManager.addObserver(
self,
forKeyPath: "online",
options: NSKeyValueObservingOptions(),
context: nil
)
}
override func observeValueForKeyPath(keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<Void>) {
if keyPath == "online" {
print("online status changed to: \(ConnectionManager.online)")
// doesn't get printed on value changes
}
}
As for now, Swift cannot have observable class properties. (In fact, static properties are just global variables with its namespace confined in a class.)
If you want to use KVO, create a shared instance (singleton class) which has online property and add observer to the instance.
I solved it with the singleton pattern suggested by #OOper.
class ConnectionManager: NSObject {
static let sharedInstance = ConnectionManager()
private override init() {} // This prevents others from using the default '()' initializer for this class.
#objc dynamic var online = false
}
Then:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.tableFooterView = UIView()
ConnectionManager.sharedInstance.addObserver(self,
forKeyPath: "online",
options: [.new, .initial],
context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if object is ConnectionManager && keyPath == "online" {
// ...
}
}
Try replacing dynamic static var online = false to #nonobjc static var online = false
What's happening is that because it inherits from NSObject, Swift is trying to generate getters and setters for it. Because you are creating it in swift, using the #nonobjc attribute solves the problem.
EDIT:
I don't believe you can observe static variables through KVO because of how it works
Here is a link and snippet from Apple's Guide on KVO
Unlike notifications that use NSNotificationCenter, there is no
central object that provides change notification for all observers.
Instead, notifications are sent directly to the observing objects when
changes are made.
Perhaps, instead of using KVO, you could declare online like:
static var online = false {
didSet{
//code to post notification through regular notification center
}
}
If you're set on using it, this question might point you towards the right direction — it'll involve diving deeper into how KVO works: Is it possible to set up KVO notifications for static variables in objective C?
I would suggest property wrapper, I tried the example below and worked perfectly for me:
#propertyWrapper
struct StaticObserver<T> {
private var value:T
init(value:T) {
self.value = value
}
var wrappedValue: T {
get {
// Do your thing
return self.value
}
set {
// Do your thing before set
self.value = newValue
// Do your thing after set
}
}
#StaticObserver(value: false)
dynamic static var online:Bool
Given an instance of a class, how can I observe a property change?
For e.g., I'm building an SDK that initializes a host app's chat view to provide more functionality with a simple inplementation that looks like:
sdk.initialize(chatView)
In that initializing function, I need to track the host app's chat-view's hidden property so that the SDK's view matches.
A simple KVO example for observing hidden:
class SDKViewController : UIViewController {
private var context = 0
private var observingView: UIView?
func initialize(view: UIView) {
removeObservations()
observingView = view
// start observing changes to hidden property of UIView
observingView?.addObserver(self, forKeyPath: "hidden", options: [.New], context: &context)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let newValue = change?[NSKeyValueChangeNewKey] as? Bool where context == &self.context {
print("hidden changed: \(newValue)")
}
}
// this is called by deinit
// it should also be called if they can deregister the view from your SDK
func removeObservations() {
if let view = observingView {
view.removeObserver(self, forKeyPath: "hidden")
observingView = nil
}
}
deinit {
removeObservations()
}
}
This is making some assumptions about your configuration, but if you allow initialization of many views, you can adjust easily.
Also, a lot of this is more concise if you use KVOController by Facebook, which is not in Swift.
Edit: Just to note, hidden does work with KVO.
Edit #2: Updated YourSDKClass to SDKViewController (NSObject -> UIViewController)
Here is an example using protocols
protocol MyClassDelegate:class {
func myClassValueDidChange(newValue:Int)
}
class MyClass {
weak var delegate:MyClassDelegate?
var value = 0 {
didSet {
delegate?.myClassValueDidChange(value)
}
}
}
class ViewController:UIViewController,MyClassDelegate {
let myClass = MyClass()
override func viewDidLoad() {
super.viewDidLoad()
myClass.delegate = self
}
func myClassValueDidChange(newValue:Int) {
//Do something
}
}
You can use Key Value Observing (KVO) to monitor changes to general properties on classes, which includes the hidden property on UIView instances. This is done using addObserver:forKeyPath:options:context: defined in the NSKeyValueObserving protocol.
Note that you can also hide a view by removing it from its superview or by setting its alpha to zero.
I was wondering is it possible to edit properties in Swift through and extension?
I want to do something like this.
extension UIGestureRecognizer {
var state: UIGestureRecognizerState {
didSet(state) {
self.stateChanged(state)
}
}
You cannot change the implementation of UIGestureRecognizer's state property. If you could publicly get and set state, then you could create a different computed property myState that forwarded get and set to state. Unfortunately you can't. You can however get around this using KVO (Key Value Observing).
First off, we create an object that can respond to KVO notifications for our gesture and implement observeValueForKeyPath which is called when the state property is called.
class StateObserver : NSObject {
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let gesture = object as? UIGestureRecognizer {
gesture.stateChanged(gesture.state)
}
}
}
Second, register a StateObserver instance to get notified when tap.state is changed.
let stateObserver = StateObserver()
let tap = UITapGestureRecognizer()
tap.addObserver(stateObserver, forKeyPath: "state", options: [.New, .Old], context: nil)
Third, don't forget to unregister StateObserver once you're done.
tap.removeObserver(stateObserver, forKeyPath: "state'")
Why doesn't deist get called on an object that has used NSNotificationCenter, I have included below a simple version of my code. Where I create an object that observes for a notification and when the notification is fired, it removes the observer's subscription. I also remove the subscription if the object is freed up. However, when running profiling for the app, you can see that after viewDidAppear finishes there is a persistent allocation for the test object that is now nil and should have been freed up. Why is this the case?
import UIKit
class ViewController: UIViewController {
var t: test?
override func viewWillAppear(animated: Bool) {
t = test()
fire()
t = nil
}
func fire() {
NSNotificationCenter.defaultCenter().postNotificationName("Hello",
object: nil)
}
}
class test {
var e: NSObjectProtocol?
init() {
e = NSNotificationCenter.defaultCenter().addObserverForName(
"Hello", object: nil, queue: NSOperationQueue.mainQueue(),
usingBlock: sayHello)
}
deinit {
if let e = e { NSNotificationCenter.defaultCenter().removeObserver(e) }
}
func sayHello(notification: NSNotification) {
if let e = e { NSNotificationCenter.defaultCenter().removeObserver(e) }
}
}
I would appreciate an answer even in Objective-C, since it will probably answer this question as well.
Thank you very much
Passing in a function of self as a closure parameter will create a retain cycle.
What you're doing is effectivity short hand for:
init() {
e = NSNotificationCenter.defaultCenter().addObserverForName("Hello", object: nil, queue: NSOperationQueue.mainQueue() { notification in
self.sayHello(notification)
}
}
As you can see self is being captured here. To get around this you should defined self as unowned in a capture list:
init() {
e = NSNotificationCenter.defaultCenter().addObserverForName("Hello", object: nil, queue: NSOperationQueue.mainQueue() { [unowned self] notification in
self.sayHello(notification)
}
}
This will prevent the retain cycle.
As you're removing the observer in sayHello you should also set e to nil in there too after removing the observer.
See this question for more info about retain cycles, capturing, etc. when using this method on NSNotificationCenter.
you don't add self as observer but another block.
BUT
Then you remove yourself (though never added) but forget the block
--- so:
self.observer = center.addObserverForName(didEnterBackground, object: nil, queue: nil) {
...
}
then later
center.removeObserver(self.observer)