So far I have developed android apps with java and Firebase. Now I want to code my first iOS app with Firebase as well. I have this Database Model:
I want to retrieve these Objects and store them in an array. My ExercisePresets Model looks like this:
class ExercisePreset {
var key : String?
var name : String?
var type : String = "Dynamic";
var progressions : [String] = [String]();
//#Exclude
var selected : Bool = false;
init() { }
convenience init(name : String, type : String) {
self.init();
self.name = name;
self.type = type;
}
}
In Java I could just get the snapshot as an Object of my Model like this:
fb.getRefUserPresets(null).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
ExcersisePresets e = dataSnapshot.getValue(ExcersisePresets.class);
e.setKey(dataSnapshot.getKey());
presets.add(e);
}
}
but I don't get how this works for swift. I tried this:
refPresets!.observe(.childAdded) { (snapshot) in
let e = snapshot.value as! ExercisePreset;
}
But I get this error:
Could not cast value of type '__NSDictionaryM' (0x109d594f8) to 'TrainingsLog.ExercisePreset' (0x106a30408).
So how do I retrieve my Objects the right way?
You have to cast it as NSDictionary
refPresets!.observe(.childAdded) { (snapshot) in
let e = snapshot.value as! NSDictionary
print(e)
}
You have to cast your snapshot.value as NSDictionary
Related
I have a json database on firebase and trying to get them and put into local array of dictionaries.
My json model on Firebase
My struct model is also like below
struct Places {
var type:String!
var country:String!
var name:String!
var image:String!
var coords:[Coords]!
init(type: String, country: String, name: String, image: String, coords: [Coords]) {
self.type = type
self.country = country
self.name = name
self.image = image
self.coords = coords
}
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String:Any]
type = snapshotValue["type"] as! String
country = snapshotValue["country"] as! String
name = snapshotValue["name"] as! String
image = snapshotValue["image"] as! String
coords = snapshotValue["coords"] as! [Coords]!
}
}
And also [Coords] struct model like below:
struct Coords {
var latStart:String!
var latEnd:String!
var lonStart:String!
var lonEnd:String!
init(latStart: String, latEnd: String, lonStart: String, lonEnd: String) {
self.latStart = latStart
self.latEnd = latEnd
self.lonStart = lonStart
self.lonEnd = lonEnd
}
}
And I am trying to get and put json data by below code:
placesRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("data not exist")
return
}
var plc: [Places] = []
for eachPlace in (snapshot.children){
let place = Places(snapshot: eachPlace as! FIRDataSnapshot)
plc.append(place)
}
self.allPlaces = plc
The problem is that I can get the array of dictionary except coords dictionary inside array of dictionary. [Coords] dictionary seems null and I would like to know what the problem is. Thanks for any suggestion.
Because snapshotValue["coords"] as! [Coords]! are not Coords yet. They are just dictionaries. You have to go through each dictionary in snapshotValue[“coords”] and init a Coords object, then when you’re finished group them all into an array and assign it to self.coords of the Places struct. The map function is really convenient for this.
Example:
I would change the Coords init function to something like:
init(dictionary: [String : AnyObject]) {
self.latStart = dictionary["lat1"] as? String
//...
//...
}
Then in Places init use something like:
coords = (snapshotValue["coords"] as? [[String : AnyObject]])?.map({ Coord(dictionary: $0) })
I didn't test this and making some assumptions here, but you should be able to make something similar work.
I get a response from the server, lets say in the following format:
{
//Array of models
"A_Key" : [
{
"B_data" : ""
}
]
//dictionary
"A_Key_2" : {
"C_data" : ""
}
"A_Key_3" : "0"
"A_Key_4" : 0
}
Now I'm trying to create a model for the above response (I don't want to use any pod or library).
So what I do is:
class A{
let A_Key : Array<B>!
let A_Key_2 : C!
let A_Key_3 : String!
let A_Key_4 : Int!
init(withDictionary dict:Dictionary<String, Any>){
self.A_Key = A.getArrayOfModel(fromArrayDictionary:dict["A_Key"] as! Array<Dictionary<String, Any>>)
self.A_Key_2 = C(withDictionary : dict["A_Key_2"] as! Dictionary<String, Any>)
self.A_Key_3 = dict["A_Key_3"] as? String ?? ""
self.A_Key_4 = dict["A_Key_4"] as? Int ?? 0
}
class func getArrayOfModel(fromArrayDictionary array:[Dictionary<String, Any>]){
var tempArray = [B]()
for dict in array{
tempArray.append(B(withDictionary: dict))
}
return tempArray
}
}
Assume there are classes B and C with appropriate init methods. Something similar to this compiles properly and all the iterations occur without any crash, so i'm assuming that this will work.
Now my query is, is this the best way to initialize the object in swift with a dictionary? As I'm concerned about a scenario in which the API responds with a nil value for any of "A_Key" (It will cause a crash i believe)?
OR
Is the following the proper way to map a json to an object in swift?
If it is, isn't it a bit tedious to check every value for nil? As the number of checks will keep increasing with having optional init methods for all nested models?
init(withDictionary dict:Dictionary<String, Any>){
if let a_key_3 = dict["A_Key_3"] as? String{
self.A_Key_3 = a_key_3
}
}
I'm receiving a JSON dictionary from a web service and I need to map the return values to existing values. Here's essentially what I'm trying to do:
class Contract {
var contractID: String?
var ebState: String?
var ibState: String?
var importerState: String?
var exportersBankRefNo: String?
var importersBankRefNo: String?
}
let contract1 = Contract()
contract1.contractID = "001"
let contract2 = Contract()
contract2.contractID = "002"
// This is the JSON return dictionary
let exportAppnStatusList: [[String: String]] = [["contractID":"001",
"ExporterBankRefNo":"ExporterBankRefNo001",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"",
"ImporterBankState":"UNKNOWN",
"ImporterState":"UNKNOWN" ],
["contractID":"002",
"ExporterBankRefNo":"ExporterBankRefNo002",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"ImporterBankRefNo002",
"ImporterBankState":"ACCEPTED",
"ImporterState":"UNKNOWN" ]]
I need to take the exportAppnStatusList and fill in the associated values in the existing contract1 and contract2, mapping by the contractID
This fills the contracts with available information, it ignores contracts where the id could not be found:
for contract in [contract1, contract2] {
if let contractDict = exportAppnStatusList.filter({$0["contractID"] == contract.contractID}).first {
contract.exportersBankRefNo = contractDict["ExporterBankRefNo"]
contract.ebState = contractDict["ExporterBankState"]
contract.importersBankRefNo = contractDict["ImporterBankRefNo"]
contract.ibState = contractDict["ImporterBankState"]
contract.importerState = contractDict["ImporterState"]
}
}
Why not generate the contract object by mapping over the array of dictionaries like this? You'll need to write a custom initializer that takes all these params
exportAppnStatusList.map { (dict:[Stirng:String]) -> Contract in
return Contract(contractID:dict["contractID"],
ebState:dict["ExporterBankState"],
ibState:dict["ImporterBankState"],
importerState:dict["ImporterState"],
exportersBankRefNo:dict["ExporterBankRefNo"],
importersBankRefNo:dict["ImporterBankRefNo"]
}
Try using this init (your class must inherit from NSObject):
init(jsonDict: [String: String]) {
super.init()
for (key, value) in jsonDict {
if class_respondsToSelector(Contract.self, NSSelectorFromString(key)) {
setValue(value, forKey: key)
}
}
}
Then you can do this:
exportAppnStatusList.forEach {
print(Contract(jsonDict: $0))
}
Considering the following model:
class Person: Object {
dynamic var name = ""
let hobbies = Dictionary<String, String>()
}
I'm trying to stock in Realm an object of type [String:String] that I got from an Alamofire request but can't since hobbies has to to be defined through let according to RealmSwift Documentation since it is a List<T>/Dictionary<T,U> kind of type.
let hobbiesToStore: [String:String]
// populate hobbiestoStore
let person = Person()
person.hobbies = hobbiesToStore
I also tried to redefine init() but always ended up with a fatal error or else.
How can I simply copy or initialize a Dictionary in RealSwift?
Am I missing something trivial here?
Dictionary is not supported as property type in Realm.
You'd need to introduce a new class, whose objects describe each a key-value-pair and to-many relationship to that as seen below:
class Person: Object {
dynamic var name = ""
let hobbies = List<Hobby>()
}
class Hobby: Object {
dynamic var name = ""
dynamic var descriptionText = ""
}
For deserialization, you'd need to map your dictionary structure in your JSON to Hobby objects and assign the key and value to the appropriate property.
I am currently emulating this by exposing an ignored Dictionary property on my model, backed by a private, persisted NSData which encapsulates a JSON representation of the dictionary:
class Model: Object {
private dynamic var dictionaryData: NSData?
var dictionary: [String: String] {
get {
guard let dictionaryData = dictionaryData else {
return [String: String]()
}
do {
let dict = try NSJSONSerialization.JSONObjectWithData(dictionaryData, options: []) as? [String: String]
return dict!
} catch {
return [String: String]()
}
}
set {
do {
let data = try NSJSONSerialization.dataWithJSONObject(newValue, options: [])
dictionaryData = data
} catch {
dictionaryData = nil
}
}
}
override static func ignoredProperties() -> [String] {
return ["dictionary"]
}
}
It might not be the most efficient way but it allows me to keep using Unbox to quickly and easily map the incoming JSON data to my local Realm model.
I would save the dictionary as JSON string in Realm. Then retrive the JSON and convert to dictionary. Use below extensions.
extension String{
func dictionaryValue() -> [String: AnyObject]
{
if let data = self.data(using: String.Encoding.utf8) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject]
return json!
} catch {
print("Error converting to JSON")
}
}
return NSDictionary() as! [String : AnyObject]
} }
and
extension NSDictionary{
func JsonString() -> String
{
do{
let jsonData: Data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
return String.init(data: jsonData, encoding: .utf8)!
}
catch
{
return "error converting"
}
}
}
UPDATE 2021
Since Realm 10.8.0, it is possible to store a dictionary in a Realm object using the Map type.
Example from the official documentation:
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var currentCity = ""
// Map of city name -> favorite park in that city
let favoriteParksByCity = Map<String, String>()
}
Perhaps a little inefficient, but works for me (example dictionary from Int->String, analogous for your example):
class DictObj: Object {
var dict : [Int:String] {
get {
if _keys.isEmpty {return [:]} // Empty dict = default; change to other if desired
else {
var ret : [Int:String] = [:];
Array(0..<(_keys.count)).map{ ret[_keys[$0].val] = _values[$0].val };
return ret;
}
}
set {
_keys.removeAll()
_values.removeAll()
_keys.appendContentsOf(newValue.keys.map({ IntObj(value: [$0]) }))
_values.appendContentsOf(newValue.values.map({ StringObj(value: [$0]) }))
}
}
var _keys = List<IntObj>();
var _values = List<StringObj>();
override static func ignoredProperties() -> [String] {
return ["dict"];
}
}
Realm can't store a List of Strings/Ints because these aren't objects, so make "fake objects":
class IntObj: Object {
dynamic var val : Int = 0;
}
class StringObj: Object {
dynamic var val : String = "";
}
Inspired by another answer here on stack overflow for storing arrays similarly (post is eluding me currently)...
I'm tring to parse a JSON format like this:
{
"key_1" : {
"key_2" : "value"
}
}
and then assign "value" to a variable.
Here is my code:
var variableShouldBeAssigned: String
if let x = (jsonResult["key_1"]? as? NSDictionary) {
if let y = (x["key_2"]? as? String) {
variableShouldBeAssigned = y
}
}
However, an error occurs when I try to downcast from x["key_2"]? to a String, but it's fine to downcast from jsonResult["key_1"]? to an NSDictionary.
I can solve the error by using x["key_2"] to replace x["key_2"]?, but I don't really know why it only works for jsonResult["key_1"]?.
Can anybody tell me the reason?
String does not conform to NSCopying, but surely NSString does!
Also, going from NSString to String is instantaneously implied...
So I would say try something like this... Change String to NSString
here is a sample, assuming that you handle the jsonResult as a NSDictionary...
func giveDictionary(jsonResult:NSDictionary) -> String?
{
if let x = (jsonResult["key_1"]? as? NSDictionary)
{
if let y = (x["key_2"]? as? NSString)
{
return y
}
}
return nil
}
You can simplify all your type checking by using a Swift dictionary at the beginning:
var variableShouldBeAssigned: String
if let dict = jsonResult as? [String:[String:String]] {
if let key1Dict = dict["key_1"] {
if let value = key1Dict["key_2"] {
variableShouldBeAssigned = value
}
}
}
In fact, you can even combine the two last if statements:
var variableShouldBeAssigned: String
if let dict = jsonResult as? [String:[String:String]] {
if let value = dict["key_1"]?["key_2"] {
variableShouldBeAssigned = value
}
}
In general, you should using Swift Dictionaries instead of NSDictionary