How do I remove "Optional()" from object in an array - swift

So im using CloudKit and fetching all the data into an array as [StartDay], my StartDay class looks like this:
import UIKit
import CloudKit
class StartDay {
var recordID: CKRecord.ID!
var wakeUp: String!
var sleptWell: String!
var dNN: String!
var created: String! {
get {
return created
}
}
}`
My function loads get an arraylist, which contains information received from the database. In my database it stands like this: "22.01.09:
func checkIfButtonShouldBeEnabled(startDayList: [StartDay]){
let startDayDates = startDayList.map{$0.created}
for i in 0..<startDayDates.count {
print(startDayDates)
}
}`
OUTPUT:
Optional("22.01.2019")
Optional("22.01.2019")
I want to remove "Optional()", so it only says "22.01.2019", how can I do so?
UPDATE: FETCH FUNC
func loadStartDay() -> [StartDay]{
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "StartDay", predicate: predicate)
let operation = CKQueryOperation(query: query)
var startDays: [StartDay] = []
operation.desiredKeys = ["wakeUp", "wellSlept", "dNN", "recordID", "createdDato"]
operation.recordFetchedBlock = { (record:CKRecord) in
let newStartDay = StartDay()
newStartDay.wakeUp = record.object(forKey: "wakeUP") as? String
newStartDay.sleptWell = record.object(forKey: "sleptWell") as? String
newStartDay.dNN = record.object(forKey: "dNN") as? String
newStartDay.recordID = record.object(forKey: "recordID") as? CKRecord.ID
newStartDay.created = record.object(forKey: "createdDato") as? String
print(newStartDay.created)
startDays.append(newStartDay)
}

You can use print(startDayDates!) or print(startDayDates ?? "default value").
But I recommend usage of startDayList.compactMap() instead of startDayList.map()to ensure your array doesn't contain nil values.
You can also do like this:
startDayList
.compactMap { $0.created }
.forEach { print($0) }

As you designed the database model you exactly know which record attributes always exist. Declaring class properties as implicit unwrapped optional as an alibi not to write an initializer is very bad practice.
Assuming every attribute in a record does have a value declare the properties as non-optional and write an initializer.
At least created and recordID are supposed to have always a value!
import UIKit
import CloudKit
class StartDay {
var recordID: CKRecord.ID
var wakeUp: String
var sleptWell: String
var dNN: String
var created: String
init(record : CKRecord) {
// recordID can be retrieved directly
self.recordID = record.recordID
self.wakeUp = record.object(forKey: "wakeUP") as! String
self.sleptWell = record.object(forKey: "sleptWell") as! String
self.dNN = record.object(forKey: "dNN") as! String
self.created = record.object(forKey: "createdDato") as! String
}
}
and create instances with
operation.recordFetchedBlock = { record in
startDays.append(StartDay(record: record))
}
Now the Optional has gone.
print(startDayList.map{ $0.created })

Related

Why converting a Firestore querySnapshot into custom objects with compactMap returns empty although the querySnapshot contains documents?

Screenshot of a Firestore Document
I am using Swift, Xcode and a Firestore database.
I created a TableView and a Custom Object Class (MediumSample) with a dictionary and want to load my Firestore documents and show them in the TableView.
The documents (example in the screenshot) are loaded from Firestore correctly but the conversion into the object did not work. The list of objects returned from compactMap is always empty.
Here is my code snippets. It would be great, if someone has a hint on what is going wrong.
Custom Object Class (simplified):
import Foundation
import FirebaseFirestore
protocol MediumSampleDocumentSerializable {
init?(dictionary: [String:Any])
}
struct MediumSample {
var id: String
var field_t: String
var field_i: Int64
var field_b1: Bool
var field_b2: Bool
var field_g: FirebaseFirestore.GeoPoint
var field_d: Date
var field_a: [String]
var usecase: String
var dictionary: [String:Any] {
return [
"id": id,
"field_t": field_t,
"field_i": field_i,
"field_b1": field_b1,
"field_b2": field_b2,
"field_g": field_g,
"field_d": field_d,
"field_a": field_a,
"usecase": usecase
]
}
}
extension MediumSample: MediumSampleDocumentSerializable {
init?(dictionary: [String:Any]) {
guard let id = dictionary ["id"] as? String,
let field_t = dictionary ["field_t"] as? String,
let field_i = dictionary ["field_i"] as? Int64,
let field_b1 = dictionary ["field_b1"] as? Bool,
let field_b2 = dictionary ["field_b2"] as? Bool,
let field_g = dictionary ["field_g"] as? FirebaseFirestore.GeoPoint,
let field_d = dictionary ["field_d"] as? Date,
let field_a = dictionary ["field_a"] as? [String],
let usecase = dictionary ["usecase"] as? String else {return nil}
self.init (id: id, field_t: field_t, field_i: field_i, field_b1: field_b1, field_b2: field_b2, field_g: field_g, field_d: field_d, field_a: field_a, usecase: usecase)
}
}
Declaration of the database and array and calling the loading function:
import UIKit
import FirebaseFirestore
class MediumTableViewController: UITableViewController {
//MARK: Properties
var db: Firestore!
var mediumsamples = [MediumSample]()
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
loadMediumSamples()
}
Function for loading the Firestore documents to fill the Array:
private func loadMediumSamples() {
//run the Firestore query
db.collection(Constants.MEDIUMS).whereField("usecase", isEqualTo: Constants.USECASE)
.getDocuments() { querySnapshot, err in
if let err = err {
print("Error getting documents: \(err)")
} else {
//initialise an array of medium objects with Firestore data snapshots
self.mediumsamples = querySnapshot!.documents.compactMap({MediumSample(dictionary: $0.data())})
//fill the tableView
DispatchQueue.main.async {
self.tableView.reloadData()
print(self.mediumsamples)
}
print("Mediums List", self.mediumsamples) // This line returns: Mediums List []
print("Mediums List size", (self.mediumsamples.count)) // This line returns: Mediums List size 0
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())") // This line returns the snapshot documents correctly!
}
}
}
}
Here is how the screenshot object object is added:
func addMediumSamples() {
let currentDateTime = Date()
let location = FirebaseFirestore.GeoPoint(latitude: 0, longitude: 0)
let mediumsample = MediumSample(id: "an id", field_t: "field_t", field_i: 10, field_b1: true, field_b2: false, field_g: location, field_d: currentDateTime, field_a: [Constants.SAMPLE_DEV], usecase: Constants.SAMPLE_DEV)
var ref: DocumentReference? = nil
ref = self.db.collection(Constants.MEDIUMS).addDocument(data: mediumsample.dictionary) {
error in
if let error = error {
print("Error writing city to Firestore: \(error)")
} else {
print("Document added with id : \(ref!.documentID)")
}
}
}
The problem is in the MediumSample struct, in the field_d type (Date).
The type of that field in your Cloud Firestore database is Timestamp.
The field "field_d" in the MediumSample struct expects a value of type Date.
You can change the type to the FirebaseFirestore.Timestamp, or you can convert it to Date when mapping and before passing to the MediumSample.
eg. for converting Timestamp to Date in Swift
let date = timestamp.dateValue()

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 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.

Iterate through the array of Dictionary and separate the name for status true

I want to iterate through this array and separate out the names that have status true.
var array = [["name":"joe", "status":false ],["name":"will", "status":false],["name":"smith" , "status":false]]
This should do the trick.
var array = [["name":"joe", "status":true ],["name":"will", "status":true],["name":"smith" , "status":false]]
let filteredDictionary = array.filter( { $0["status"] as? Bool ?? false } )
var names = [String]()
for dictionary in filteredDictionary {
if let nameFound = dictionary["name"] as? String {
names.append(nameFound)
}
}
I recommend that you use a struct to store the values instead of a dictionary. Something like this.
struct Person {
var name: String
var status: Bool
}
If you have it like this in a Person array, it becomes less complicated as dictionary value optional handling can be avoided.
var personArray = [Person(name: "joe", status: true), Person(name: "will", status: false)
let names = personArray.filter( {$0.status} ).map( {$0.name} )
You can use a simple filter is you want to keep both the status and name, otherwise use compactMap is you just want to keep the name.
let statuses = [["name":"joe", "status":true ],["name":"will", "status":false],["name":"smith" , "status":false]]
let trueStatuses = statuses.filter({$0["status"] as? Bool == true}) // [["name": "joe", "status": true]]
let namesWithTrueStatus = statuses.compactMap{$0["status"] as? Bool == true ? $0["name"] as? String : nil} //["joe"]

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