How to get selector name as an attribute on an object in Swift? - swift

When working with rest JSON-APIs its convenient to keep usage of an attribute consistent throught the app and when calling the server
For this example:
class Picture: NSManagedObject {
#NSManaged var copyright: String
#NSManaged var likes: NSNumber
}
func postToServer() {
let newPicture = Picture(copyright: "haaakon", likes:433)
let arguments = #{
newPicture.copyright.selectorName : newPicture.copyright,
newPicture.likes.selectorName : newPicture.likes
}
println(arguments)
}
println should give this output:
{ "copyright": "haaakon", "likes": "433" }
How to go about when creating an extension (or other solution) on a given attribute that returns basically the same as:
NSStringFromSelector(Selector(newPicture.copyright)) // returns "copyright"
newPicture.copyright.selectorName // returns "copyright"

Try this:
extension Picture{
func dictionary() -> Dictionary<String, AnyObject> {
var outCount: CUnsignedInt = 0
let properties: UnsafeMutablePointer<objc_property_t> = class_copyPropertyList(self.dynamicType, &outCount)
var result: Dictionary<String, AnyObject> = Dictionary()
for var i = 0; i < Int(outCount); i++ {
var property : NSString! = NSString(CString: property_getName(properties[i]), encoding: NSUTF8StringEncoding)
result[property] = self.valueForKey(property)
}
return result
}
}

Related

Nil data returned when copying "working" json data to new struc array

Weird. I swear this was working but then it just stopped working .. or ... Please ignore the i+i ,I will clean this up...
I don't have a clue why but myrecords?[i].title is returning nil. The json.releases[i].date_adde is working fine and full of data. I can "print" it and get a result. but when I go to copy it to the myrecords it is returning nil.
I download the data from JSON, that works fine. then I try to copy the data to a struc array I can get to in other parts of my app and now my myrecords data is empty. what the heck am I doing wrong?
import Foundation
var numberOfRecords : Int = 0
struct routine {
var dateadded : String
var title : String
var artist : String
var year : Int
var recordlabel : String
}
var myrecords: [routine]?
//-------------------------------------------------------------
struct Response: Codable {
var pagination: MyResult
var releases: [MyReleases]
}
public struct MyResult: Codable {
var page: Int
var per_page: Int
var items: Int
}
public struct MyReleases: Codable {
var date_added: String
var basic_information: BasicInformation
}
public struct BasicInformation: Codable {
var title: String
var year: Int
var artists : [Artist]
var labels: [Label]
}
public struct Artist: Codable {
var name: String
}
public struct Label: Codable {
var name: String
}
let url = "https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?callback=&sort=added&sort_order=desc&per_page=1000"
public func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
//HAVE DATA
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("Converion Error:\(error.localizedDescription)")
}
guard let json = result else {
return
}
numberOfRecords = json.pagination.items
var i: Int
i = -1
for _ in json.releases {
i = i + 1
myrecords?[i].dateadded = json.releases[i].date_added
myrecords?[i].title = json.releases[i].basic_information.title
myrecords?[i].artist = json.releases[i].basic_information.artists[0].name
myrecords?[i].year = json.releases[i].basic_information.year
myrecords?[i].recordlabel = json.releases[i].basic_information.labels[0].name
print(">>>>>>\(myrecords?[i].dateadded)")
}
})
task.resume()
}
You haven't initialized myrecords array.
Otherwise, you cannot use subscript like myrecords[i] when you don't know the capacity of array, it can be out of index.
First, initialize your array.
var myrecords: [routine]? = []
Second, append new element to array instead of using subscript
for _ in json.releases {
let newRecord = routine()
newRecord.dateadded = json.releases[i].date_added
newRecord.title = json.releases[i].basic_information.title
newRecord.artist = json.releases[i].basic_information.artists[0].name
newRecord.year = json.releases[i].basic_information.year
newRecord.recordlabel = json.releases[i].basic_information.labels[0].name
myrecords.append(newRecord)
}
This is the answer. :) THANK YOU All for pointing me in the right direction
struct Routine {
var dateadded : String
var title : String
var artist : String
var year : Int
var recordlabel : String
}
var myRecords: [Routine] = []
var i : Int
i = -1
for _ in json.releases {
var newRecord = Routine.self(dateadded: "", title: "", artist: "", year: 0, recordlabel: "")
i = i + 1
newRecord.dateadded = json.releases[i].date_added
newRecord.title = json.releases[i].basic_information.title
newRecord.artist = json.releases[i].basic_information.artists[0].name
newRecord.year = json.releases[i].basic_information.year
newRecord.recordlabel = json.releases[i].basic_information.labels[0].name
myRecords.append(newRecord)
}
print(">>>>\(myRecords[0].dateadded)")
I will clean up the bad code too but it works and that is good! :)

How to store and retrieve variable with custom type

I have a variable identity with type ETIdentity that I need to store in ViewController1 and retrieve in ViewController2,
ViewController1
//Variables
var activationCodeFromCore, serialNumberFromCore, entityNameFromCore, deviceIdFromCore, registrationCodeFromCore, entityFromCore: String?
var activationCode, serialNumber, entityName, deviceId, registrationCode, entity: String?
var counter: Int = 0
var storedIdentity: ETIdentity?
Below is the storedIdentity that I need to keep
let storedIdentity = BridgeSDKUtils.performClassicActivation("26586-05858", withActivationCode: "8998-6857-1357-1870", "entidad0");
GlobalIdentity.identity = storedIdentity;
func softTokenDataService() {
let storedIdentity = BridgeSDKUtils.performClassicActivation("26586-05858", withActivationCode: "8998-6857-1357-1870", "entidad0");
GlobalIdentity.identity = storedIdentity;
self.activationCode = "8998-6857-1357-1870"
self.serialNumber = "26586-05858"
self.entityName = "entityData\(counter)"
self.deviceId = "\(String(describing: storedIdentity?.deviceId))"
self.registrationCode = "\(String(describing: storedIdentity?.registrationCode))"
self.entity = "\(storedIdentity!)"
}
...
func getEntityCore()
{
//Variables that are going to be stored
self.activationCodeFromCore = activationCode
self.serialNumberFromCore = serialNumber
self.entityNameFromCore = entityName
self.deviceIdFromCore = deviceId
self.registrationCodeFromCore = registrationCode
self.entityFromCore = storedIdentity
}
...
//SecureStorage Function
func saveEntityToCoreData()-> Bool {
self.softTokenDataService()
var SavedItem:Bool = true
var arr : [[String: Any]] = [[
"activationCode": self.activationCodeFromCore,
"serialNumber": self.serialNumberFromCore,
"entityName": self.entityNameFromCore,
"deviceId": self.deviceIdFromCore,
"registrationCode": self.registrationCodeFromCore,
"entity": self.entityFromCore]]
let jsonData = try! JSONSerialization.data(withJSONObject: arr, options: [.prettyPrinted])
let json = String(data: jsonData, encoding: String.Encoding.utf8)!
if self.saveRutSwitchOn
{
SecureData.save(key: "entityData0)", data: json.data(using: .utf8)!)
}
SavedItem = self.saveRutSwitchOn
return SavedItem
}
ViewController2
struct Person {
var activationCode: String
var serialNumber: String
var entityName: String
var deviceId: String
var registrationCode: String
var entity: String
}
struct EntityModel: Codable {
let activationCode, serialNumber, entityName, deviceId, registrationCode, entity: String?
}
if let loadedData = SecureData.load(key: "entityData0") {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let entityData = try decoder.decode([EntityModel].self, from: loadedData)
entityData.forEach { (EntityModel) in
//Here I Imagine something like this
//var identity: ETIdentity?
//identity = EntityModel.entity
////Here I have the identity, so I can manipulate it like needed, because is from type ETIdentity I can access its methods.
//identity?.getOTP(Date())
}
} catch {
print(error)
}
}
Reference Data:
GlobalIdentity.swift
struct GlobalIdentity{
static var identity : ETIdentity?
}
ETIdentity.h
#interface ETIdentity : NSObject<NSCoding> {
#private
-(NSString*)getOTP:(NSDate*)time;
#end
EDIT
The problem is that the variable entity (where I need to call its parameters in ViewController2), is not a String, so it crashes, it doesn't work. I also tried to put the variable identity with the type I needed var identity: ETIdentity?, but ETIdentity isn't in protocol with Codable (to work with struct so I can call them in ViewController2)
I read your question and found basic thing that you are missing is your entity seems to be another model like a dictionary or some other type if it is dictionary then do this. Currently your SecureData.load(key: "entityData0") contain all the data that return array of entity models, And your Data object loadedData must contain entity object, Why not you try to implement Codable for your entity like this.
struct SortedArryModel: Codable {
var sorterarrkey: String? // if your array contain strings
}
And use this SortedArryModel in your EntityModel
after that you can get your array like [key:string], just convert that string to array.
Suggestion: in your case better to use JsonSerialization instead of codable, if you still want to apply codable then your elements should conform codable protocol. Like I mentioned above, implement your entity as model that confrom codable protocol.

Can I use Swift's map() on Protocols?

I have some model code where I have some Thoughts that i want to read and write to plists. I have the following code:
protocol Note {
var body: String { get }
var author: String { get }
var favorite: Bool { get set }
var creationDate: Date { get }
var id: UUID { get }
var plistRepresentation: [String: Any] { get }
init(plist: [String: Any])
}
struct Thought: Note {
let body: String
let author: String
var favorite: Bool
let creationDate: Date
let id: UUID
}
extension Thought {
var plistRepresentation: [String: Any] {
return [
"body": body as Any,
"author": author as Any,
"favorite": favorite as Any,
"creationDate": creationDate as Any,
"id": id.uuidString as Any
]
}
init(plist: [String: Any]) {
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = UUID(uuidString: plist["id"] as! String)!
}
}
for my data model, then down in my data write controller I have this method:
func fetchNotes() -> [Note] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: Any]] else {
return []
}
return notePlists.map(Note.init(plist:))
}
For some reason the line return notePlists.map(Note.init(plist:)) gives the error 'map' produces '[T]', not the expected contextual result type '[Note]'
However, If I replace the line with return notePlists.map(Thought.init(plist:)) I have no issues. Clearly I can't map the initializer of a protocol? Why not and what's an alternate solution?
If you expect to have multiple types conforming to Note and would like to know which type of note it is stored in your dictionary you need to add an enumeration to your protocol with all your note types.
enum NoteType {
case thought
}
add it to your protocol.
protocol Note {
var noteType: NoteType { get }
// ...
}
and add it to your Note objects:
struct Thought: Note {
let noteType: NoteType = .thought
// ...
}
This way you can read this property from your dictionary and map it accordingly.

Multiple Realm objects to JSON

I am trying to convert Realm Object into JSON. My version is working but not if you want to put multiple objects into JSON. So my question is, how should you add multiple Realm Objects into JSON?
Something like that:
{
"Users": [
{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}
],
"Posts": [
{"id": "1","title": "hey"},{"id": "2","title": "hey2"},{"id": "3","title": "hey3"}
]
}
This is what I am doing right now:
func getRealmJSON(name: String, realmObject: Object, realmType: Any) -> String {
do {
let realm = try Realm()
let table = realm.objects(realmType as! Object.Type)
if table.count == 0 {return "Empty Table"}
let mirrored_object = Mirror(reflecting: realmObject)
var properties = [String]()
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String! {
properties.append(property_name)
}
}
var jsonObject = "{\"\(name)\": ["
for i in 1...table.count {
var str = "{"
var insideStr = String()
for property in properties {
let filteredTable = table.value(forKey: property) as! [Any]
insideStr += "\"\(property)\": \"\(filteredTable[i - 1])\","
}
let index = insideStr.characters.index(insideStr.startIndex, offsetBy: (insideStr.count - 2))
insideStr = String(insideStr[...index])
str += "\(insideStr)},"
jsonObject.append(str)
}
let index = jsonObject.characters.index(jsonObject.startIndex, offsetBy: (jsonObject.count - 2))
jsonObject = "\(String(jsonObject[...index]))]}"
return jsonObject
}catch let error { print("\(error)") }
return "Problem reading Realm"
}
Above function does like that, which is good for only one object:
{"Users": [{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}]}
Like this I call it out:
let users = getRealmJSON(name: "Users", realmObject: Users(), realmType: Users.self)
let posts = getRealmJSON(name: "Posts", realmObject: Posts(), realmType: Posts.self)
And I tried to attach them.
Can anybody please lead me to the right track?
You can use data models to encode/decode your db data:
For example you have
class UserEntity: Object {
#objc dynamic var id: String = ""
#objc dynamic var createdAt: Date = Date()
#objc private dynamic var addressEntities = List<AddressEntity>()
var addresses: [Address] {
get {
return addressEntities.map { Address(entity: $0) }
}
set {
addressEntities.removeAll()
let newEntities = newValue.map { AddressEntity(address: $0) }
addressEntities.append(objectsIn: newEntities)
}
}
}
Here you hide addressEntities with private and declare addresses var with Address struct type to map entities into proper values;
And then use
struct User: Codable {
let id: String
let createdAt: Date
let addresses: [Address]
}
And then encode User struct any way you want

Swift error when trying to access Dictionary: `Could not find member 'subscript'`

This won't compile:
I've tried a couple different things; different ways of declaring the Dictionary, changing its type to match the nested-ness of the data. I also tried explicitly saying my Any was a collection so it could be subscripted. No dice.
import UIKit
import Foundation
class CurrencyManager {
var response = Dictionary<String,Any>()
var symbols = []
struct Static {
static var token : dispatch_once_t = 0
static var instance : CurrencyManager?
}
class var shared: CurrencyManager {
dispatch_once(&Static.token) { Static.instance = CurrencyManager() }
return Static.instance!
}
init(){
assert(Static.instance == nil, "Singleton already initialized!")
getRates()
}
func defaultCurrency() -> String {
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleCountryCode) as String
let codesToCountries :Dictionary = [ "US":"USD" ]
if let localCurrency = codesToCountries[countryCode]{
return localCurrency
}
return "USD"
}
func updateBadgeCurrency() {
let chanCurr = defaultCurrency()
var currVal :Float = valueForCurrency(chanCurr, exchange: "Coinbase")!
UIApplication.sharedApplication().applicationIconBadgeNumber = Int(currVal)
}
func getRates() {
//Network code here
valueForCurrency("", exchange: "")
}
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"][exchange][currency] as Float
}
}
Let's take a look at
response["current_rates"][exchange][currency]
response is declared as Dictionary<String,Any>(), so after the first subscript you try to call another two subscripts on an object of type Any.
Solution 1. Change the type of response to be a nested dictionary. Note that I added the question marks because anytime you access a dictionary item you get back an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String, Float>>>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"]?[exchange]?[currency]
}
Solution 2. Cast each level to a Dictionary as you parse. Make sure to still check if optional values exist.
var response = Dictionary<String,Any>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
let exchanges = response["current_rates"] as? Dictionary<String,Any>
let currencies = exchanges?[exchange] as? Dictionary<String,Any>
return currencies?[currency] as? Float
}
You can get nested dictionary data by following these steps:
let imageData: NSDictionary = userInfo["picture"]?["data"]? as NSDictionary
let profilePic = imageData["url"] as? String
func valueForCurrency(currency :String, exchange :String) -> Float? {
if let exchanges = response["current_rates"] as? Dictionary<String,Any> {
if let currencies = exchanges[exchange] as? Dictionary<String,Any> {
return currencies[currency] as? Float
}
}
return nil
}
response is declared as such:
var response = Dictionary<String,Any>()
So the compiler thinks response["current_rates"] will return an Any. Which may or may not be something that is subscript indexable.
You should be able to define you type with nested Dictionaries, 3 levels and eventually you get to a float. You also need to drill in with optional chaining since the dictionary may or may not have a value for that key, so it's subscript accessor returns an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String,Float>>>()
// ... populate dictionaries
println(response["current_rates"]?["a"]?["b"]) // The float