2018-12-30 15:01:23.228731+0200 iChat[51679:726127] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key username.'
(lldb)
Using firebase dictionary to setvalue for a key to NSobject class
import UIKit
class User: NSObject {
var email: String?
var username: String?
}
Function
func fetchUsers() {
Database.database().reference().child("users").observe(.childAdded) { (snap) in
if let dictionary = snap.value as? [String : AnyObject]{
let user = User()
user.setValuesForKeys(dictionary)
print(user.username)
}
}
}
Objective-C inference has been changed in Swift 4. You have to add the #objc attribute to each property
class User: NSObject {
#objc var email: String?
#objc var username: String?
}
However setValuesForKeys is very objective-c-ish. There are better (and more light-weight) ways in Swift without the heavy ObjC runtime.
And why are both properties optional? Are users allowed without name and email?
Consider that with non-optionals the compiler will throw an error at compile time if you are going to pass nil.
Related
I'm developing app using CLCircularRegion.
I want to set value in CLCircularRegion. So I did region.setValue(forKey:key) but it showed
if CLLocationManager.locationServicesEnabled() {
for obj in pushInfoArr! {
let localLatiStr = obj["local_latit"] as! String
let localLongitStr = obj["local_longit"] as! String
let localMsg = obj["local_mesg"] as! String
let url = obj["cnnct_url"] as! String
let localLati = Double(localLatiStr)
let localLongi = Double(localLongitStr)
let center = CLLocationCoordinate2DMake(localLati!, localLongi!)
region = CLCircularRegion(center: center, radius: 1000, identifier: localMsg)
region.setValue(localMsg, forKey: "msg")
region.setValue(url, forKey: "url")
region.notifyOnEntry = true
self.locationManager.startMonitoring(for: region)
}
}
"Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[
setValue:forUndefinedKey:]: this class is not key value
coding-compliant for the key bb"
Could I set value(url) in CLCircularRegion??
Swift isn't Javascript - you can't create new properties on an existing class or struct, and neither CLCircularRegion nor CLRegion from which it inherits has properties msg nor url.
You have misconstrued what setValue does - it is a method on the base class NSObject which allows the setting of an existing property of the class by reference to its "name" or key. Its use is not recommended in Swift (it's an Objective-C legacy).
What you need to do is to create a struct or subclass that holds both your region and your required properties:
Either
struct MyRegion {
var region: CLCircularRegion
var msg: String
var url: String
}
or
class MyRegion: CLCircularRegion {
var msg: String
var url: String
init(centre: CLLocationCoordinate2D, radius: Double, identifier: String, msg: String, url: String) {
super.init(centre: centre, radius: radius, identifier: identifier)
self.msg = msg
self.url = url
}
}
p.s. don't use 'url' as the name of a property that isn't a URL - call it 'urlString' or some such. I just used that to correspond to your question's terminology.
No, CLCircularRegion doesn't have a property called url, so you can't set url on objects of that type.
I usually would create an NSObject like the example below make a firebase observe single event call to get data, then I would create an instance of the NSObject and simply call the setValueForKeys method on the NSObject and the values will be passed in then I could easily use an if let statement to get the specific data I required. this has stopped working since I upgraded to swift 4.0.2 from swift 3.1 see code snippet below. I believe I am doing this wrong way, since the new update. As the key value it requires does exist as can be seen in the NSObject declaration and also the print out. Any suggestion will be appreciated.
class BarberMarker: NSObject {
var companyID: String?
var companyName: String?
var companyLogoImageUrl: String?
var companyLogoImage: UIImage?
var companyFormattedAddress: String?
var companyAddressLongitude: String?
var companyAddressLatitude: String?
var distanceFromCurrentUser: Double?
var dateCreated: String?
var timezone: String?
var calendar: String?
var local: String?
var ratingValue: CGFloat?
}
firebase call to get data from database
func getAllMarkers(){
firebaseRef.child("barberMarkers").observeSingleEvent(of: .value, with: { (snapshotssshhh) in
if let dictionary = snapshotssshhh.value as? [String: AnyObject] {
for marker in dictionary {
if let locMarker = marker.value as? [String: AnyObject] {
var markerB = BarberMarker()
print(locMarker)
markerB.setValuesForKeys(locMarker)
self.barbermarkers.append(markerB)
guard let lon = markerB.companyAddressLongitude, let lat = markerB.companyAddressLatitude else {
return
}
let latitude = (lat as NSString).doubleValue
let longitude = (lon as NSString).doubleValue
let locValue:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
DispatchQueue.main.async{
let desiredMarker = GMSMarker(position: locValue)
desiredMarker.icon = GMSMarker.markerImage(with: UIColor(r: 118, g: 187, b: 220))
desiredMarker.map = self.mapView
self.bMarkers.append(desiredMarker)
}
}
}
}
}, withCancel: nil)
}
error message I get
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<maizewars.BarberMarker 0x7fb62ff5f5b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key companyAddressLongitude.'
try this bro.
#objcMembers class BarberMarker: NSObject {
...
}
reason #objcMembers Use in Swift 4
When a Swift class introduces many new methods or properties that require behavior from the Objective-C runtime, use the #objcMembers attribute in the declaration of that class.
Applying the #objcMembers attribute to a class implicitly adds the #objc attribute to all of its Objective-C compatible members.
Because applying the #objc attribute can increase the compiled size of an app and adversely affect performance, only apply the #objcMembers attribute on declarations when each member needs to have the #objc attribute applied.
I have my current code as listed below:
class ViewController:UIViewController{
ovveride func viewDidLoad(){
filterData()
}
func filterData(){
var usersArray = [User(name:"John",userId:1),User(name:"Ed",userId:2),User(name:"Ron",userId:3)]
let filteredData = (userArray as NSArray).filtered(using:NSPredicate(format: "userId=1"))
}
}
The above code throws throws the following Exception (NSUnknownKeyException):
The Object Type '[<ViewController.User 0x6000000afa20> valueForUndefinedKey:]':
this class is not key value coding-compliant for the key userId.
The document of Apple for filter(using:) does not specify any changes that could be causing this issue.
How can I use NSPredicate in Swift 4.0?
Also, as requested, I tried using #objc. But, still the same error.
#objc
class User:NSObject{
var name:String!
var userId:Int!
init(name:String,userId:Int){
self.name = name
self.userId = userId
}
}
With Further Comments received for this question, on adding #objc attribute to the userId I receive the below message.
property cannot be marked #objc because its type cannot be represented in Objective-C
#objc
class User:NSObject{
#objc var name:String!
var userId:Int! //#objc here results in error
init(name:String,userId:Int){
self.name = name
self.userId = userId
}
}
String property NSPredicate it works completely fine.
- Add #objc to class
- Also, add #objc to property
Why not just use filter? It's much more "Swift-y" and doesn't involve converting your array to an NSArray.
let filteredData = usersArray.filter { $0.userId == 1 }
In my Firebase project I'm trying to apply Model View Controller pattern, so I separated the controller and the class that handles firebase requests.
I get this exception
exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber length]: unrecognized selector sent to instance
I'm trying to get news items from the database. Here is the model
class NewsItem: NSObject{
var title: String?
var detail: String?
var photoUrl: String?
var timestamp: String?
}
Here is the firebase handler class
protocol NewsController: class {
func fetchedNews(_ newsItem: NewsItem)
}
class FirebaseHandler {
private static let _instance = FirebaseHandler()
static var Instance: FirebaseHandler{
return _instance
}
weak var newsControllerDelegate: NewsController?
func fetchNews() {
References.Instance.newsRef.observe(.childAdded) {
(snapshot: DataSnapshot) in
if let child = snapshot.value as? [String: AnyObject]{
let newsItem = NewsItem()
print("CHILD: \n\n\n\n\(child)\n\n\n")
newsItem.setValuesForKeys(child)
DispatchQueue.main.async {
self.newsControllerDelegate?.fetchedNews(newsItem)
}
}
}
}
}
I can get the child values printed fine, but the problem is when I call the protocol delegate method.
Here are some portions of the table view controller class where I adopt the NewsController protocol:
FirebaseHandler.Instance.newsControllerDelegate = self
FirebaseHandler.Instance.fetchNews()
Then I implement the method:
func fetchedNews(_ newsItem: NewsItem) {
print("Item:\n\n\n\(newsItem)\n\n\n")
self.newsItems.append(newsItem)
self.tableView.reloadData()
}
The newsItem isn't printed since the error occurs before this method is called I guess. Any suggestions are appreciated.
From the reported NSNumber related error, I would guess your timestamp property is actually stored as an integer in Firebase (and not as a string). If this is the case, try changing it to:
var timestamp: Int = 0
To understand why we can't use Int? (or even Int!) above please see this answer as well.
Also: you don't need that DispatchQueue.main.async wrapper in your observer code. All database callbacks are already called on the main thread by Firebase ;)
I have built a custom class that looks something like this:
import UIKit
class Device: NSObject {
var id = String()
var name = String()
var type = String()
//var description = String() //Cannot override with a stored property 'description'
}
Very simple class, and I am inheriting NSObject so I can use "setValue(value, forKey: keyName)". However, when I do the following:
device.setValue("My Description", forKey: "description")
I am getting the following error:
'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key description.'
So insummary, I can't override the NSObject.description, but when I try to set it I am getting an error. Anyone run into this before?
Look at where description is defined. It is listed in the NSObjectProtocol as
public var description: String { get }
You can only get the description property, you can't set it.
In Objective-C, description is implemented in most classes as a method; it has no underlined storage. The swift equivalent would be a computed property:
public override var description: String {
return "I'm an object"
}
tl;dr Use a computed property instead of a stored property
class CustomObject : NSObject {
private var des: String
override var description: String {
get {
return des
}
set(newValue) {
des = newValue
}
}
init(string: String) {
des = string
}
}
You can only get the description property, you can't set it.
In Objective-C string classes, the class description for NSMutableString specifies that the class inherits from NSString. description, when you try to set description it will be getting an error.
Method 1
While using setValue(value, forKey: keyName) Use can store property value by using.
class ObjName: NSObject{
var id: String?
var descriptions : String?
override var description: String {
get {
return self.description
}
set(newvalue) {
descriptions = newvalue
}
}
}
Using above code, method setValue for key description value store into the descriptions. while you get the value you can use descriptions. Also, it does not affect on description get.
Method 2
Overriding setValue function. like below.
class ObjName: NSObject{
var id: String?
var descriptions : String?
override func setValue(_ value: Any?, forKey key: String) {
if key == "description"{
if let desc = value as? String{
self.descriptions = String()
self.descriptions = desc
}
}else{
super.setValue(value, forKey: key)
}
}
}