Is there a way to make observable enum in Swift (KVO)
Asked Answered
R

4

7

I'm trying to use RxSwift for binding in MVVM. I have a Enum:

enum Color : Int {
    case Red = 0, Green
}

and class for test

class Test : NSObject {
    var color: Color = .Red
    dynamic var test: String? {
        didSet {
            print("didSet \(test)")
        }
    }
}

And want to observe changes like:

test.rx_observe(Color.self, "color").subscribeNext { (color) -> Void in
     print("Observer \(color)")
}.addDisposableTo(bag)

But the program chashes with *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<RDProject.Test 0x7ff373513020> addObserver:<RxCocoa.KVOObserver 0x7ff37351a420> forKeyPath:@"color" options:5 context:0x0] was sent to an object that is not KVC-compliant for the "color" property.'

Code for simple String works:

test.rx_observe(String.self, "test").subscribeNext { string in
     print("Observer \(string)")
}.addDisposableTo(bag)

test.test = "1"
test.test = "2"

I found here that to make class inherited not from NSObject I should make it dynamic, but I can't make Enum dynamic. Is there a way to make Enum observable?

Reeve answered 16/3, 2016 at 9:31 Comment(0)
S
5

You don't need to use KVO for this task. Just use a BehaviorSubject like this:

Create a private Field like this one:

let colorSubject = BehaviorSubject<Color?>(value: nil)

Then you have a property like this which informs the BehaviorSubject that the value did change.

var color : Color? {
    didSet {
        colorSubject.onNext(color)
    }
}

To subscribe to any change use an equivalent statement to this one:

let disposable = colorSubject.subscribeNext { (color: Color?) -> Void in
    // Do something with it.
}
Stinkpot answered 16/3, 2016 at 10:26 Comment(0)
M
2

Because your enum is of type Int, you can make it objective-c compatible by marking it with @objc. Doing this will make compiler ok with marking the property as dynamic. For the property to be KVO compliant, it'll also need to be anotated with @objc.

@objc enum Color : Int {
    case Red = 0, Green
}

class Test : NSObject {
    @objc dynamic var color: Color = .Red
    dynamic var test: String? {
        didSet {
            print("didSet \(test)")
        }
    }
}
Mcgregor answered 16/3, 2016 at 10:1 Comment(3)
Hi. I'm using Swift 5 and tried this solution. Unfortunately it crashes with Fatal error: Could not extract a String from KeyPath Swift.ReferenceWritableKeyPath ... Could you confirm this still works?Whose
It indeed crashes, but annotating the field with @objc fixes the crash and makes this functionning again. I updated my answer to reflect this. Hope it help :)Mcgregor
Thanks for the response! --- This indeed fixes the problem. First of all, it's an enum of type Error, changing that to Int is the first step. --- Then, annotating both the enum and property with @objc are the is the last step for making this work. --- And then a problem I still have is the the property is MyEnumType?, so an Optional. This can't be represented in Objective-C. I don't think that can be solved. But you answered my question, and this works :-)Whose
T
0

I can suggest only make a proxy variable and use KVO on it.

class Model: NSObject {

    enum Color: Int {
        case Red = 0, Green
    }

    dynamic var colorRaw: Int?
    var color: Color = .Red {
        didSet {
            colorRaw = color.rawValue
        }
    }

}

More details here - https://christiantietze.de/posts/2015/05/observing-enum-swift-kvo/

Torse answered 16/3, 2016 at 9:42 Comment(0)
X
0

If your applications's minimum supported version of iOS is 13.0 or newer you can use Combine and make the property @Published

class Test : NSObject {
    @Published var color: Color = .Red

    dynamic var test: String? {
        didSet {
            print("didSet \(test)")
        }
    }
}

and observe it like this:

let test = Test()
test.$color.sink(receiveValue: { color in
    print(color)
})
Xhosa answered 22/9, 2023 at 14:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.