swiftkvc

Is there a way to access an array element in KVC using value(forKeyPath:)?


Suppose I have the some KVC compliant objects similar to the following:

class Person : NSObject {
   var office: Office
   var firstName: String
   var lastName: String
   var reports: [Report]

   init( office: Office, 
         firstName: String,
         lastName: String,
         reports: [Report] ) {

      ...

   }
}

class Office : NSObject {
   var building: String
   var room: Int

   init( building: String, 
         room: Int ) {

      ...

   }

}

class Report : NSObject {
   var title: String
   var contents: String 

   init( title: String, 
         contents: String ) {

      ...

   }
}

And I create an instance, person

let person = Person(
                office: Office( 
                    building: "Main",
                    room: 2 ),
                firstName: "Bob",
                lastName: "Roberts",
                reports: [
                    Report( title: "Today's weather", contents: "..." ),
                    Report( title: "Traffic", contents: "..." ),
                    Report( title: "Stocks", contents: "..." )
                ] )

I can use person.value(forKeyPath:) to access properties and nested properties of person as follows:

person.value(forKeyPath: "firstName") // "Bob"
person.value(forKeyPath: "office.room" ) // 2

However, is there a way in KVC to the title from the second report?

Something like

person.value(forKeyPath: "reports[1].title" ) // "Traffic"

Solution

  • It's possible with Swift's native KVC even with structs, the ObjC runtime is not needed

    struct Person {
        var office: Office
        var firstName: String
        var lastName: String
        var reports: [Report]
    }
    
    struct Office {
        var building: String
        var room: Int
    }
    
    struct Report {
        var title: String
        var contents: String
    }
    
    let person = Person(office: Office(building: "Main", room: 2 ),
                        firstName: "Bob", lastName: "Roberts",
                        reports: [
                            Report( title: "Today's weather", contents: "..." ),
                            Report( title: "Traffic", contents: "..." ),
                            Report( title: "Stocks", contents: "..." )
        ])
    
    
    let keyPath = \Person.reports[1].title
    let title = person[keyPath: keyPath] // "Traffic"