Could I set value in CLCircularRegion? - swift

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.

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.

Can't get NSKeyedArchiver to work: unrecognized selector [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
In a UITableView when deleting a row and saving the data source I also want to save to persistent storage.
I'm using NSKeyedArchiver to save an array of my custom object type Participant and it fails straight away with:
2018-02-13 23:01:41.818945+0000 ProjectName[28680:5720571]
-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60800004d290
2018-02-13 23:01:41.826553+0000 ProjectName[28680:5720571] *** Terminating app due to uncaught
exception 'NSInvalidArgumentException', reason: '-[_SwiftValue
encodeWithCoder:]: unrecognized selector sent to instance
0x60800004d290'
I've searched everywhere and can't find anything that's applicable to me. So far as I can tell my data class for Participant object conforms to NSObject and NSCoding with the necessary methods implemented.
EDIT: Thank you all for your feedback so quickly. Code is below and as I said before, if I've neglected to post something necessary that likely speaks to my experience so any help gratefully received!
Data Class (excerpt)
class Participant: NSObject, NSCoding {
//MARK: Properties
var name: String
var jobTitle: String?
var picture: UIImage?
var rate: Float
var ratePeriod: ratePeriods
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("participants")
//MARK: Types
struct PropertyKey {
//THESE MUST NOT CHANGE ONCE BUILT
static let name = "name"
static let jobTitle = "jobTitle"
static let picture = "picture"
static let rate = "rate"
static let ratePeriod = "ratePeriod"
}
//MARK: Initialisation
init?(name: String, jobTitle: String?, picture: UIImage?, rate: Float?, ratePeriod: ratePeriods?) {
//The name must not be empty
guard !name.isEmpty else {
return nil
}
//Init stored properties
self.name = name
self.jobTitle = jobTitle ?? ""
self.picture = picture
self.rate = rate ?? 0.0
self.ratePeriod = ratePeriod ?? .annually
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(jobTitle, forKey: PropertyKey.jobTitle)
aCoder.encode(picture, forKey: PropertyKey.picture)
aCoder.encode(rate, forKey: PropertyKey.rate)
aCoder.encode(ratePeriod, forKey: PropertyKey.ratePeriod)
}
required convenience init?(coder aDecoder: NSCoder) {
//The name is required. If we cannot decode a name string, the init should fail.
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String else {
return nil
}
let jobTitle = aDecoder.decodeObject(forKey: PropertyKey.jobTitle) as? String
let picture = aDecoder.decodeObject(forKey: PropertyKey.picture) as? UIImage
let rate = aDecoder.decodeFloat(forKey: PropertyKey.rate)
let ratePeriod = aDecoder.decodeObject(forKey: PropertyKey.ratePeriod) as? ratePeriods
//Must call designated init
self.init(name: name, jobTitle: jobTitle, picture: picture, rate: rate, ratePeriod: ratePeriod)
}
}
From inside Private Func where I'm trying to make use of it and it breaks
let archivePath = Participant.ArchiveURL.path
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(participants, toFile: archivePath)
Thank you again for any help I can get on this.
EDIT 2: I've done a bit more debugging and I guess my inexperience with the Xcode IDE and debugging has hindered this but it looks to be the ratePeriod property of the object being stored that's throwing the error. This property is a Struct which I think somebody else leaned to as being an issue. Is this resolvable or do I need to look at a different method for persisting storage of Structs?
EDIT 3: I've solved the problem but don't know how to mark this as resolved. The issue wasn't the Struct (which I wasn't storing) but rather an Enum. When using NSKeyedArchiver and NSCoding to store Enums you need to store the .rawValue and reconstitute it as an Int. ie: -
Encoding
aCoder.encode(ratePeriod.rawValue, forKey: PropertyKey.ratePeriod)
and
Decoding
let ratePeriodIntValue = (aDecoder.decodeObject(forKey: PropertyKey.ratePeriod) as? Int) ?? ratePeriods.annually.rawValue
let ratePeriod = ratePeriods(rawValue: ratePeriodIntValue)
Your custom object type, Participant, needs to be a class (not a struct), and in particular it must be a subclass of NSObject. Now you can adopt NSCoding successfully.
There is a very simple article on NSHipster about how to make your custom classes NSCoding/NSKeyedArchiver compliant. I recommend giving that a look.
As a basic answer, in order to make your object work with NSKeyedArchiver you need to tell the coder/decoder how to encode/decode your object. For example, if this is your class:
class Participant {
let name: String
let id: String
let age: Int
init(name: String, id: String, age: Int) {
self.name = name
self.id = id
self.age = age
}
}
You would need to make the following modifications to make it work with NSKeyedArchiver. First, declare that you are conforming to NSObject and NSCoding protocols. Then implement the encode methods and the convenience init?(coder:_) initializer:
class Participant: NSObject, NSCoding {
let name: String
let id: String
let age: Int
init(name: String, id: String, age: Int) {
self.name = name
self.id = id
self.age = age
}
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String,
let id = aDecoder.decodeObject(forKey: "id") as? String,
let age = aDecoder.decodeObject(forKey: "age") as? Int else { return nil }
self.init(name: name, id: id, age: age)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.id, forKey: "id")
aCoder.encode(self.age, forKey: "age")
}
}
Try adding #objc to your swift equivalent encodeWithCoder: method. Ditto for the decode init method. I doubt it's the solution but might be.
Also, please show how you wrote the NSCoding methods in Swift. Maybe they don't match up, given the inherent conversion between Swift and ObjC for how they are to be written?
func encode(with aCoder: NSCoder) {

NSObject setValuesForKeys coding-complaint error in Swift 4.0.2

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.

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 to construct convenience init? in Swift [duplicate]

With the following code I try to define a simple model class and it's failable initializer, which takes a (json-) dictionary as parameter. The initializer should return nil if the user name is not defined in the original json.
1.
Why doesn't the code compile? The error message says:
All stored properties of a class instance must be initialized before returning nil from an initializer.
That doesn't make sense. Why should I initialize those properties when I plan to return nil?
2.
Is my approach the right one or would there be other ideas or common patterns to achieve my goal?
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
if let value: String = dictionary["user_name"] as? String {
userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
}
}
That doesn't make sense. Why should I initialize those properties when
I plan to return nil?
According to Chris Lattner this is a bug. Here is what he says:
This is an implementation limitation in the swift 1.1 compiler,
documented in the release notes. The compiler is currently unable to
destroy partially initialized classes in all cases, so it disallows
formation of a situation where it would have to. We consider this a
bug to be fixed in future releases, not a feature.
Source
EDIT:
So swift is now open source and according to this changelog it is fixed now in snapshots of swift 2.2
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
Update: From the Swift 2.2 Change Log (released March 21, 2016):
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
For Swift 2.1 and earlier:
According to Apple's documentation (and your compiler error), a class must initialize all its stored properties before returning nil from a failable initializer:
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Note: It actually works fine for structures and enumerations, just not classes.
The suggested way to handle stored properties that can't be initialized before the initializer fails is to declare them as implicitly unwrapped optionals.
Example from the docs:
class Product {
let name: String!
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
In the example above, the name property of the Product class is
defined as having an implicitly unwrapped optional string type
(String!). Because it is of an optional type, this means that the name
property has a default value of nil before it is assigned a specific
value during initialization. This default value of nil in turn means
that all of the properties introduced by the Product class have a
valid initial value. As a result, the failable initializer for Product
can trigger an initialization failure at the start of the initializer
if it is passed an empty string, before assigning a specific value to
the name property within the initializer.
In your case, however, simply defining userName as a String! does not fix the compile error because you still need to worry about initializing the properties on your base class, NSObject. Luckily, with userName defined as a String!, you can actually call super.init() before you return nil which will init your NSObject base class and fix the compile error.
class User: NSObject {
let userName: String!
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
super.init()
if let value = dictionary["user_name"] as? String {
self.userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
self.isSuperUser = value
}
self.someDetails = dictionary["some_details"] as? Array
}
}
I accept that Mike S's answer is Apple's recommendation, but I don't think it's best practice. The whole point of a strong type system is to move runtime errors to compile time. This "solution" defeats that purpose. IMHO, better would be to go ahead and initialize the username to "" and then check it after the super.init(). If blank userNames are allowed, then set a flag.
class User: NSObject {
let userName: String = ""
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: [String: AnyObject]) {
if let user_name = dictionary["user_name"] as? String {
userName = user_name
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
if userName.isEmpty {
return nil
}
}
}
Another way to circumvent the limitation is to work with a class-functions to do the initialisation.
You might even want to move that function to an extension:
class User: NSObject {
let username: String
let isSuperUser: Bool
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
super.init()
}
}
extension User {
class func fromDictionary(dictionary: NSDictionary) -> User? {
if let username: String = dictionary["user_name"] as? String {
let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
let someDetails = dictionary["some_details"] as? [String]
return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
}
return nil
}
}
Using it would become:
if let user = User.fromDictionary(someDict) {
// Party hard
}
Although Swift 2.2 has been released and you no longer have to fully initialize the object before failing the initializer, you need to hold your horses until https://bugs.swift.org/browse/SR-704 is fixed.
I found out this can be done in Swift 1.2
There are some conditions:
Required properties should be declared as implicitly unwrapped optionals
Assign a value to your required properties exactly once. This value may be nil.
Then call super.init() if your class is inheriting from another class.
After all your required properties have been assigned a value, check if their value is as expected. If not, return nil.
Example:
class ClassName: NSObject {
let property: String!
init?(propertyValue: String?) {
self.property = propertyValue
super.init()
if self.property == nil {
return nil
}
}
}
A failable initializer for a value type (that is, a structure or
enumeration) can trigger an initialization failure at any point within
its initializer implementation
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/sg/jEUH0.l
You can use convenience init:
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
}
convenience init? (dict: NSDictionary) {
guard let userName = dictionary["user_name"] as? String else { return nil }
guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
guard let someDetails = dictionary["some_details"] as? [String] else { return nil }
self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
}
}