NSObject setValuesForKeys coding-complaint error in Swift 4.0.2 - swift

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.

Related

NSUnknownKeyExeption using firebase and NSobject

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.

Could I set value in CLCircularRegion?

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.

iOS Populate Table View With Firebase Data Using MVC Pattern

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 ;)

Swift: Unable To Set Description on Subclass of NSObject

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)
}
}
}

How do I create global object in swift?

I think my previous question was too vague. Let me try again.
I'm trying to hold user information by creating a singleton.
After a bit of research, this is what I understood.
Class UserInfo {
var userFirstName: String?
var userLastName: String?
/*I'll add all other variables here*/
}
let sharedInfo = UserInfo()
Am I on right path?
EDIT: For Swift 1.2 and higher (Thanks Will M)
class UserInfo {
static let sharedUserInfo = UserInfo()
var firstName: String?
var lastName: String?
}
For Swift earlier than 1.2
Something along the lines of:
class UserInfo
{
// Singleton Setup
private static var info: UserInfo?
class func sharedUserInfo() -> UserInfo
{
if self.info == nil
{
self.info = UserInfo()
}
return self.info!
}
// Class properties
var firstName: String?
var lastName: String?
}
let user = UserInfo.sharedUserInfo()
user.firstName = "Fred"
user.lastName = "Smith"
I would also recommend making sure that a singleton is really what you want, because depending on how you use it, you could run into some race conditions with threading.