How to store and retrieve variable with custom type - swift

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.

Related

how to get single variable name from struct

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

How can I save an object of a custom class in Userdefaults in swift 5/ Xcode 10.2

I want to save the array patientList in UserDefaults. Patient is an custom class so I need to transfer it into Data object, but this doesn't work on Swift 5 like it did before.
func addFirstPatient(){
let newPatient = Patient(name: nameField.text!, number: numberField.text!, resultArray: resultArray, diagnoseArray: diagnoseArray)
let patientList: [Patient] = [newPatient]
let encodeData: Data = NSKeyedArchiver.archivedData(withRootObject: patientList)
UserDefaults.standard.set(encodeData, forKey: "patientList")
UserDefaults.standard.synchronize()
}
struct Patient {
var diagnoseArray: [Diagnose]
var resultArray: [Diagnose]
var name: String
var number: String
init(name: String, number: String, resultArray: [Diagnose], diagnoseArray: [Diagnose]) {
self.diagnoseArray = diagnoseArray
self.name = name
self.number = number
self.resultArray = resultArray
}
}
struct Diagnose{
var name: String
var treatments: [Treatment]
var isPositiv = false
var isExtended = false
init(name: String, treatments: [Treatment]) {
self.name = name
self.treatments = treatments
}
}
struct Treatment {
var name: String
var wasMade = false
init(name: String) {
self.name = name
}
}
This is what the function looks like.
The problem is in the line where I initialize encodeData.
let encodeData: Data = try! NSKeyedArchiver.archivedData(withRootObject: patientList, requiringSecureCoding: false)
This is what Swift suggests but when I try it like this it always crashes and I don't get the error
You cannot use NSKeyedArchiver with structs at all. The objects must be subclasses of NSObject which adopt NSCoding and implement the required methods.
As suggested in the comments Codable is the better choice for example
struct Patient : Codable {
var name: String
var number: String
var resultArray: [Diagnose]
var diagnoseArray: [Diagnose]
}
struct Diagnose : Codable {
var name: String
var treatments: [Treatment]
var isPositiv : Bool
var isExtended : Bool
}
struct Treatment : Codable {
var name: String
var wasMade : Bool
}
let newPatient = Patient(name: "John Doe",
number: "123",
resultArray: [Diagnose(name: "Result", treatments: [Treatment(name: "Treat1", wasMade: false)], isPositiv: false, isExtended: false)],
diagnoseArray: [Diagnose(name: "Diagnose", treatments: [Treatment(name: "Treat2", wasMade: false)], isPositiv: false, isExtended: false)])
let patientList: [Patient] = [newPatient]
do {
let encodeData = try JSONEncoder().encode(patientList)
UserDefaults.standard.set(encodeData, forKey: "patientList")
// synchronize is not needed
} catch { print(error) }
If you want to provide default values for the Bool values you have to write an initializer.
Vadian's answer is correct, you cannot use NSKeyedArchiver with structs. Having all your objects conform to Codable is the best way to reproduce the behavior you are looking for. I do what Vadian does, but I you can also use protocol extensions to make this safer.
import UIKit
struct Patient: Codable {
var name: String
var number: String
var resultArray: [Diagnose]
var diagnoseArray: [Diagnose]
}
struct Diagnose: Codable {
var name: String
var treatments: [Treatment]
var isPositiv : Bool
var isExtended : Bool
}
struct Treatment: Codable {
var name: String
var wasMade : Bool
}
let newPatient = Patient(name: "John Doe",
number: "123",
resultArray: [Diagnose(name: "Result", treatments: [Treatment(name: "Treat1", wasMade: false)], isPositiv: false, isExtended: false)],
diagnoseArray: [Diagnose(name: "Diagnose", treatments: [Treatment(name: "Treat2", wasMade: false)], isPositiv: false, isExtended: false)])
let patientList: [Patient] = [newPatient]
Introduce a protocol to manage the encoding and saving of objects.
This does not have to inherit from Codable but it does for this example for simplicity.
/// Objects conforming to `CanSaveToDisk` have a save method and provide keys for saving individual objects or a list of objects.
protocol CanSaveToDisk: Codable {
/// Provide default logic for encoding this value.
static var defaultEncoder: JSONEncoder { get }
/// This key is used to save the individual object to disk. This works best by using a unique identifier.
var storageKeyForObject: String { get }
/// This key is used to save a list of these objects to disk. Any array of items conforming to `CanSaveToDisk` has the option to save as well.
static var storageKeyForListofObjects: String { get }
/// Persists the object to disk.
///
/// - Throws: useful to throw an error from an encoder or a custom error if you use stage different from user defaults like the keychain
func save() throws
}
Using protocol extensions we add an option to save an array of these objects.
extension Array where Element: CanSaveToDisk {
func dataValue() throws -> Data {
return try Element.defaultEncoder.encode(self)
}
func save() throws {
let storage = UserDefaults.standard
storage.set(try dataValue(), forKey: Element.storageKeyForListofObjects)
}
}
We extend our patient object so it can know what to do when saving.
I use "storage" so that this could be swapped with NSKeychain. If you are saving sensitive data (like patient information) you should be using the keychain instead of UserDefaults. Also, make sure you comply with security and privacy best practices for health data in whatever market you're offering your app. Laws can be a very different experience between countries. UserDefaults might not be safe enough storage.
There are lots of great keychain wrappers to make things easier. UserDefaults simply sets data using a key. The Keychain does the same. A wrapper like https://github.com/evgenyneu/keychain-swift will behave similar to how I use UserDefaults below. I have commented out what the equivalent use would look like for completeness.
extension Patient: CanSaveToDisk {
static var defaultEncoder: JSONEncoder {
let encoder = JSONEncoder()
// add additional customization here
// like dates or data handling
return encoder
}
var storageKeyForObject: String {
// "com.myapp.patient.123"
return "com.myapp.patient.\(number)"
}
static var storageKeyForListofObjects: String {
return "com.myapp.patientList"
}
func save() throws {
// you could also save to the keychain easily
//let keychain = KeychainSwift()
//keychain.set(dataObject, forKey: storageKeyForObject)
let data = try Patient.defaultEncoder.encode(self)
let storage = UserDefaults.standard
storage.setValue(data, forKey: storageKeyForObject)
}
}
Saving is simplified, check out the 2 examples below!
do {
// saving just one patient record
// this saves this patient to the storageKeyForObject
try patientList.first?.save()
// saving the entire list
try patientList.save()
} catch { print(error) }
struct Employee: Codable{
var name: String
}
var emp1 = Employee(name: "John")
let encoder = JSONEncoder()
do {
let data = try encoder.encode(emp1)
UserDefaults.standard.set(data, forKey: "employee")
UserDefaults.standard.synchronize()
} catch {
print("error")
}

UTF-8 encoding issue of JSONSerialization

I was trying convert struct to Dictionary in Swift. This was my code:
extension Encodable {
var dictionary: [String: Any]? {
if let data = try? JSONEncoder().encode(self) {
if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
return dict
}
return nil
}
return nil
}
}
This works in most situation. But when I try to convert a nested structure which contains unicode characters such as Chinese, this happened:
struct PersonModel: Codable {
var job: String?
var contacts: [ContactSimpleModel]
var manager: ManagerSimpleModel?
}
struct ContactSimpleModel: Codable {
var relation: String
var name: String
}
struct ManagerSimpleModel: Codable {
var name: String
var age: Int
}
let contact1 = ContactSimpleModel(relation: "朋友", name: "宙斯")
let contact2 = ContactSimpleModel(relation: "同学", name: "奥丁")
let manager = ManagerSimpleModel(name: "拉斐尔", age: 31)
let job = "火枪手"
let person = PersonModel(job: job, contacts: [contact1, contact2], manager: manager)
if let dict = person.dictionary {
print(dict)
}
The result of this code is this:
["contacts": <__NSArrayI 0x600002471980>(
{
name = "\U5b99\U65af";
relation = "\U670b\U53cb";
},
{
name = "\U5965\U4e01";
relation = "\U540c\U5b66";
}
)
, "manager": {
age = 31;
name = "\U62c9\U6590\U5c14";
}, "job": 火枪手]
You can see the result. The Chinese characters in those nested structures were become a utf-8 encoding string. The top-level property "job": 火枪手 is right. But the values in those nested structures were not the original string.
Is this a bug of JSONSerialization? Or how to make it right?
More information. I used the result like this:
var sortedQuery = ""
if let dict = person.dictionary {
sortedQuery = dict.sorted(by: {$0.0 < $1.0})
.map({ "\($0)\($1)" })
.joined(separator: "")
}
It was used to check whether the query was legal. The result is not the same as Java or other platform.
The result is perfectly fine. That's the internal string representation – a pre-Unicode legacy – of an array or dictionary when you print it.
Assign the values to a label or text view and you will see the expected characters.

Decode Json Data using JsonDecoder and Alamofire

I am trying decode Json data into my Model.
This is my model
struct Devices : Codable {
var id :String?
var description :String?
var status : Int?
}
var heroes = Devices()
print(DeviceId)
let loginParam: [String: Any] = [
"id": DeviceId
]
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 5
manager.request("http://13.13.13.004/website/api/Customer/DeviceControl", method: .post , parameters: loginParam, encoding: JSONEncoding.prettyPrinted)
.responseData { response in
let json = response.data
do{
let decoder = JSONDecoder()
//using the array to put values
heroes = try decoder.decode(Devices.self, from: json!)
}catch let err{
print(err)
}
this code doesn't get in catch block.
But heroes values return nill.
When ı try use NsDictionary
Its give result.
This is a common mistake: You forget the root object
struct Root : Decodable {
private enum CodingKeys: String, CodingKey { case resultCount, devices = "results" }
let resultCount : Int
let devices : [Device]
}
And name the device struct in singular form (devices is an array of Device instances) and declare the members as non-optional
struct Device : Decodable {
var id : String
var description : String
var status : Int
}
...
var heroes = [Device]()
...
let result = try decoder.decode(Root.self, from: json!)
heroes = result.devices

How to convert Dictionary to JSON in Vapor 3?

In Vapor 1.5 I used to convert Dictionary to JSON as shown below.
How should I do it in Vapor 3.1?
In docs it says I need to create struct type and conform it to codable protocol.
Is there another method that would enable to convert the existing dictionary without creating new struct?
func makeCustomJSON(clientData: DataFromClientChargeWithCard, paymentID:String,customer: String) throws -> JSON {
var dictionaryOfStrings = [String:String]()
dictionaryOfStrings["BookingState"] = "Active"
dictionaryOfStrings["BookingStateTimeStamp"] = clientData.TimeStampBookingSavedInDB
dictionaryOfStrings["ChangesMadeBy"] = "User"
dictionaryOfStrings["PaymentID"] = paymentID
dictionaryOfStrings["DateAndTime"] = clientData.DateAndTime
dictionaryOfStrings["RatePriceClient"] = clientData.RatePriceClient
dictionaryOfStrings["Locality"] = clientData.Locality
dictionaryOfStrings["StripeCustomerID"] = customer
//some more properties below...
let response:JSON
do {
response = try JSON(node: dictionaryOfStrings)
}catch let error as NSError {
let message = "dictionaryOfStrings can't be converted in JSON"
drop.log.error(message)
logErr.prints(message: message, code: error.code)
throw NSError(domain: "errorDictionary", code: 400, userInfo: ["error" : error.localizedDescription])
}
return response
}
If you have a type like the struct that I have defined here you can simply return it, as long as it conforms to Content.
import Vapor
import FluentSQLite
struct Employee: Codable {
var id: Int?
var name: String
init(name:String) {
self.name = name
}
}
extension Employee: SQLiteModel {}
extension Employee: Migration {}
extension Employee: Parameter {}
extension Employee: Content { }
and then you can simply define a route and return it.
router.get("test") { req in
return Employee(name: "Alan")
}
if you want to use dictionary you can use the JSONSerialization and return a string.
router.get("test2") { req -> String in
let employeeDic: [String : Any] = ["name":"Alan", "age":27]
do {
let data = try JSONSerialization.data(withJSONObject: employeeDic, options: .prettyPrinted)
let jsonString = String(data: data, encoding: .utf8)
return jsonString ?? "FAILED"
}catch{
return "ERROR"
}
}