Home > Mobile >  NSKeyValueCoding.setValue(_:forKey:) not working in SwiftUI
NSKeyValueCoding.setValue(_:forKey:) not working in SwiftUI

Time:10-16

I am unable to produce a proper minimal working example, mainly due to my novice level understanding of iOS development, but I do have a simple SwiftUI project that may help.

In my ContentView.swift:

import SwiftUI

struct ContentView: View {
    @State var viewText :String
    var myClass :MyClass
    
    var body: some View {
        VStack{
            
            Text(viewText)
            .padding()
            Button("Update Text", action: {
                myClass.update()
                viewText = myClass.txt
            })
                
        
        }
    
    }
}

class MyClass: NSObject {
    var txt :String = ""
    var useSetVal :Bool = false
    
    func update(){
        if(useSetVal){
            setValue("used set val", forKey: "txt")
        } else {
            txt = "used ="
        }
        useSetVal = !useSetVal
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let mc = MyClass()
        ContentView(viewText: "", myClass: mc)
    }
}

and in my PracticeApp.swift

import SwiftUI

@main
struct PracticeApp: App {
    var body: some Scene {
        WindowGroup {
            let mc = MyClass()
            ContentView(viewText: "", myClass: mc)
        }
    }
}

In this app, I expect to see the text toggle between "used =" and "used setVal" as I push the button. Instead, I get an exception when I call setValue:

Thread 1: "[<Practice.MyClass 0x60000259dc20> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key txt."

I've been reviewing the answers here but since most answers refer to xib and storyboard files (which I either don't have, or don't know how to find), I don't see how they relate.

By the way, even though the app I'm actually trying to fix doesn't use SwiftUI and the issue with setValue is different, it's still true that I either don't have .xib or .storyboard files or I just don't know where to find them.

I'd appreciate help from any one who could either help me figure out the issue with my example, or who can get me closer to solving the issue with my actual app (including how to produce a proper MWE).

I believe what I've already written is sufficient for the issue (at least for a start), but for those interested, I thought I'd add the full story.

The Full Story

I'm new to iOS development, and I've just taken ownership of an old iOS app. It hasn't really been touched since 2017. I noticed an animation that is not working. Though I cannot verify that it ever did work, I have good reason to assume that it once did, but I can't say when it stopped working.

One issue I noticed is that animated properties are supposed to be updated with the NSKeyValueCoding.setValue(_:forKey:) function, but nothing seems to happen when the function is called.

I was able to work around the issue by overriding the setValue function with my own which basically uses a switch statement to map each key to its corresponding value. However, this did not fix the animation or explain why the setValue function isn't working.

Because both the setValue function and the CABasicAnimation.add(_:forKey:) rely on the same keyPath, I wonder if solving one issue might help me solve the other. I've decided to focus on the setValue issue (at least for now).

When I went to work starting a new project to use as an MWE, I noticed that neither the Storyboard nor the SwiftUI interface options provided by Xcode 13.0 (13A233) started me out with a project structure that matched my existing project. It was clear to me that SwiftUI was new and very different from my existing project, but the Storyboard interface wasn't familiar either and after several minutes a reading tutorials, I failed to build a storyboard app that would respond to button presses at all (all the storyboard app tutorials I found seemed to be set up for older versions of Xcode).

CodePudding user response:

SwiftUI will require that you use @ObservedObject to react to changes in an object. You can make this compliant with both observedobject and key-value manipulation as follows:

struct ContentView: View {
    @State var viewText :String
    @ObservedObject var myClass :MyClass
    
    var body: some View {
        VStack{
            
            Text(viewText)
            .padding()
            Button("Update Text", action: {
                myClass.update()
                viewText = myClass.txt
            })
                
        
        }
    
    }
}

class MyClass: NSObject, ObservableObject {
    @objc dynamic var txt: String = ""
    @Published var useSetVal: Bool = false
    
    func update(){
        if(useSetVal){
            setValue("used set val", forKey: "txt")
        } else {
            txt = "used ="
        }
        useSetVal = !useSetVal
    }
}

CodePudding user response:

You need to make the txt property available to Objective-C, in order to make it work with KVO/KVC. This is required as the Key-Value Observing/Coding mechanism is an Objective-C feature.

So, either

class MyClass: NSObject {
    @objc var txt: String = ""

, or

@objcMembers class MyClass: NSObject {
    var txt: String = ""

This will fix the error, and should make your app behave as expected. However, as others have said, you need to make more changes to the code in order to adhere to the SwiftUI paradigms.

  • Related