Cannot assign value of 'Bool' to type 'CKRecordValue?'

Swift 3 in Xcode 8 beta 6 throws the above error when trying to store `Bool`s, `Int`s or `String`s in a `CKRecord`.


It worked fine in Xcode 8 beta 5. What's the reasoning behind breaking it like this? I would think storing a `String` in a `CKRecord` would be pretty commonplace.

Replies

I have never used CloudKit, and I don't know why String and Bool don't conform to CKRecordValue, but one of the reasons may be that Swift 3 is changing quickly, and CloudKit may not have been updated properly to account for the changes.


However, I tried this, and a workaround is to wrap your values in NSString or NSNumber like this:


foo.setObject(NSString(string: "Smith"), forKey: "familyName")
foo.setObject(NSNumber(value: true), forKey: "hasShoes")


Since I don't have any test environment for CloudKit, I don't know if it will work properly, but given that it emulates the old behaviour and doesn't crash, it should work for you.

You don't need to do that with String, you can just cast to NSString like this:


`foo.setObject("smith" as NSString, forKey: "familyName")`


The reason I think is because previously in Swift 3 String would automatically be converted to NSString when needed, but now that's changed, and it's broken a lot of stuff. Now files are littered with massive amounts of casts. I've quickly gone from loving Swift 3 to despising it and I think Apple/the community made a bad move here.


It's not CloudKit that needs to be updated in any case, `CKRecordValue` is a protocol so it is the conforming objects that need to be updated.


The only way I can see to make it slightly less horrendous is by creating some extensions, but I think it's dumb it's the developer's responsibility for this given it's a standard framework.


extension String {

  var CKRecordValue: CKRecordValue {
    return self as NSString
  }

}
extension Bool {

  var CKRecordValue: CKRecordValue {
    return self as NSNumber
  }

}
extension Int {

  var CKRecordValue: CKRecordValue {
    return self as NSNumber
  }

}
extension Data {

  var CKRecordValue: CKRecordValue {
    return self as NSData
  }

}

If you meant to discuss this, please create a discussion instead of a question. Creating a question makes it look like you have a problem that you want to have resolved. I genuinely thought that you wanted help getting your code to work, and not just complain about Swift.


Regarding what needs to be updated: conformance to CKRecordValue is in CloudKit, not Foundation, so it is CloudKit that should be updated.


Also, Swift 3 is under development, so your complaint that it changes from version to version seems misplaced.

I did need help, you gave a solution and I iterated on it.


CloudKit doesn't need to be updated, no update to CloudKit could make Foundation objects conform without an update to Foundation, so it's Foundation that would need to be updated now that String and Bool aren't automatically bridged. Something I didn't know until I looked into why it has to be manually cast given the solution you provided, which again, I improved upon.

CloudKit doesn't need to be updated, no update to CloudKit could make Foundation objects conform without an update to Foundation …

Ah um, that’s not quite true. The CloudKit overlay could enable this support via a process with the fancy name of retroactive modelling. You should file a bug against CloudKit requesting improvements in this space (please post your bug number). In the meantime, you could fix this yourself via the same means. For example:

import CloudKit

protocol MyCKRecordValueType {
    var asObject: CKRecordValue { get }
}

extension CKRecord {
    func set<ValueType where ValueType : MyCKRecordValueType>(value: ValueType, forKey key: String) {
        let object = value.asObject
        self.setObject(object, forKey: key)
    }
}

extension String : MyCKRecordValueType {
    var asObject: CKRecordValue { return self as NSString }
}

let record: CKRecord! = nil
record.set("value", forKey: "key")

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Tried quin's suggestion in the final iOS 10 release of Swift3. It generated a compile error; when taking the suggested fix from the compiler, it did not appear to work. Still required casting.

Try this:

import CloudKit 

protocol MyCKRecordValueType { 
    var asObject: CKRecordValue { get } 
} 

extension CKRecord { 
    func set<ValueType>(value: ValueType, forKey key: String) where ValueType : MyCKRecordValueType { 
        let object = value.asObject 
        self.setObject(object, forKey: key) 
    } 
} 

extension String : MyCKRecordValueType { 
    var asObject: CKRecordValue { return self as NSString } 
} 

let record: CKRecord! = nil 
record.set(value: "value", forKey: "key")

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"