I am trying to use firebase for iOS and I am in front of a problem.
I am using the following JSON tree :
{
"expressions" : {
"expr0" : {
"id" : 0,
"text" : "Text0"
},
"expr1" : {
"id" : 1,
"text" : "Text1"
},
"expr2" : {
"id" : 2,
"text" : "Text2"
},
"expr3" : {
"id" : 3,
"text" : "Text3"
}
},
"levels" : {
"level0" : {
"coverImage" : "lvl0",
"expressions" : {
"expr0" : true,
"expr2" : true
},
"id" : 0,
"title" : "Level0"
},
"level1" : {
"coverImage" : "lvl1",
"expressions" : {
"expr0" : true,
"expr1" : true,
"expr3" : true
},
"id" : 1,
"title" : "Level1"
},
"level2" : {
"coverImage" : "lvl2",
"expressions" : {
"expr1" : true,
"expr2" : true,
"expr3" : true
},
"id" : 2,
"title" : "Level2"
},
"level3" : {
"coverImage" : "lvl3",
"expressions" : {
"expr3" : true
},
"id" : 3,
"title" : "Level3"
}
}
}
I have two kind of objects, expressions and levels. Here are the data models of those 2 objects :
struct Level {
let id : Int
let coverImage : String
let title : String
let expressions : [Expression]
}
struct Expression {
let id : Int
let expression : String
}
I also have a DataService singleton to provide me Firebase Database References :
import UIKit
import FirebaseDatabase
class DataService {
private static let _instance = DataService()
static var instance: DataService {
return _instance
}
var mainRef: FIRDatabaseReference {
return FIRDatabase.database().reference()
}
var levelsRef: FIRDatabaseReference {
return mainRef.child("levels")
}
var expressionsRef: FIRDatabaseReference {
return mainRef.child("expressions")
}
}
I want to store on a Levels array the level with their corresponded expressions and display this array with a collectionView. Based on the JSON file, the final result must be :
levelsArr[Level(id: 0, coverImage: "lvl0", title: "Level0", expressions: [Expression(id: 0, expression: "Text0"), Expression(id: 2, expression: "Text2")]), Level(id: 1, coverImage: "lvl1", title: "Level1", expressions: [Expression(id: 0, expression: "Text0"), Expression(id: 1, expression: "Text1"), Expression(id: 3, expression: "Text3")]), Level(id: 2, coverImage: "lvl2", title: "Level2", expressions: [Expression(id: 1, expression: "Text1"), Expression(id: 3, expression: "Text3"), Expression(id: 2, expression: "Text2")]), Level(id: 3, coverImage: "lvl3", title: "Level3", expressions: [Expression(id: 3, expression: "Text3")])]
Here is the following function I have for the moment :
private var levelsArr = [Level]()
func fetchLevels() {
DataService.instance.levelsRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let levels = snapshot.value as? Dictionary<String, AnyObject> {
for(_, valueLvl) in levels {
if let dictLvl = valueLvl as? Dictionary<String, AnyObject> {
if let id = dictLvl["id"] as? Int, let coverImage = dictLvl["coverImage"] as? String, let title = dictLvl["title"] as? String {
var expressionsArr = [Expression]()
if let expr = dictLvl["expressions"] as? Dictionary<String, AnyObject> {
for(keyLvlExp, _) in expr {
DataService.instance.expressionsRef.observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
if let expressions = snapshot.value as? Dictionary<String, AnyObject> {
for (keyExpr, valueExpr) in expressions {
if keyLvlExp == keyExpr {
if let dictExpr = valueExpr as? Dictionary<String, AnyObject> {
if let id = dictExpr["id"] as? Int, let text = dictExpr["text"] as? String{
let expression = Expression(id: id, expression: text)
expressionsArr.append(expression)
}
}
}
}
}
let level = Level(id: id, coverImage: coverImage, title: title, expressions: expressionsArr)
self.levelsArr.append(level)
print(level)
})
}
}
}
}
}
}
})
DispatchQueue.main.async{
self.collectionView?.reloadData()
}
}
The function fetchLevels() is called in the viewDidLoad().
The result of the print(snapshot) is :
Snap (expressions) {
expr0 = {
id = 0;
text = Text0;
};
expr1 = {
id = 1;
text = Text1;
};
expr2 = {
id = 2;
text = Text2;
};
expr3 = {
id = 3;
text = Text3;
};
}
Thank you.
Related
How to assign dictionary [String: [Any]] to [FirstModel]?
A function return [String:[Any]] I want to assign this to [FirstModel] struct class?
// Reponse Data.
{
"Student" : [
{
"code" : "111",
"id" : "2022-1001",
"name" : "Kiran",
"type_id" : "009-110-123"
}
],
"Subject" : [
{
"code" : "111",
"id" : "2022-2002",
"name" : "Computer Science",
"type_id" : "009-110-123"
}
]
}
Here is Solution which I was written:
func getData(data: RootModel.ResponseFirstModel? = nil) -> [ResultModel]{
let dict: [String : [RootModel.ArrayModel]] = data?.dictionary ?? [:]
var resultArray = [ResultModel]()
var arrayModel = ArrayModel()
for each in dict {
arrayModel = ArrayModel.init(code: each.value.first?.code, id: each.value.first?.id, name: each.value.first?.name, typeId: each.value.first?.typeId)
resultArray.append(.init(name: each.key, values: [arrayModel]))
}
return resultArray
}
//Declare ResultModel Class below.
struct ResultModel {
var name: String?
var values: [ArrayModel]?
}
struct ArrayModel {
var code: String?
var id: String?
var name: String?
var typeId: String?
}
You can't assign a [String: [Any]] dictionary to a [FirstModel] array. They are unrelated types.
The following two forms of data were successfully requested.
{
"ride_fare": 1000,
"km": 7
]
}
{
"ride_fare": 1000,
"km": 7,
"options": [ 0, 1, 2]
}
However, I don't know how to request a two-dimensional associative array like the one below.
How can I request it?
{
"ride_fare": 1000,
"km": 7,
"option_fares": [
{
"price": 200,
"name": "立ち寄り",
"id": 1
}
]
}
The code that I wrote:
var options = [Any]()
for option in optionFares {
let params = [
"id" : option.id ?? 0,
"name" : option.name ?? "",
"price" : option.price ?? 0
] as [String : Any]
options.append(params)
}
let faresData = [
"id" : driverOrder.id ?? 0,
"km" : driverOrder.distance ?? 0,
"option_fares" : options,
"ride_fare" : driverOrder.ride_fare ?? 0
] as [String : Any]
First, create a struct that matches the json format you want to request.
struct Params: Codable {
let rideFare, km: Int
let optionFares: [OptionFare]
enum CodingKeys: String, CodingKey {
case rideFare = "ride_fare"
case km
case optionFares = "option_fares"
}
}
struct OptionFare: Codable {
let price: Int
let name: String
let id: Int
}
And you must create a request parameter in Moya's task.
import Moya
extension APITarget: TargetType {
var task: Task {
case .yourCaseName(let price, let name, let id, let rideFare, let km):
let encoder: JSONEncoder = JSONEncoder()
let optionFareData: [OptionFare] = []
optionFareData.append(OptionFare(price, name, id))
let paramsData = Params(rideFare, km, optionFareData)
let jsonData: Data = try! encoder.encode(paramsData)
return .requestData(jsonData)
}
}
}
I’m stuck with some problem. I have a JSON response from server:
{
"days" : [
{
"id" : 1,
"name" : "Day 1 - first day",
"url": "http://example.com/days/1"
},
{
"id" : 2,
"name" : "Day 2 - second day",
"url": "http://example.com/days/2"
},
...
],
"week" : [
{
"id" : 1,
"dayIds" : [1, 2, 6, 9, 23, 44, 2345],
"name" : "Rest week"
},
{
"id" : 35,
"dayIds" : [34,77,23,67,126,224],
"name" : "Educational week"
},
],
"plan" : {
"weekIds: [1, 6, 23, 74]
}
}
My data models (without mapping):
class Day: Object {
#objc dynamic var id: Int = -1
#objc dynamic var name: String = ""
#objc dynamic var url: String = ""
}
class Week: Object {
var dayIds = List<String>()
#objc dynamic var name: String = ""
#objc dynamic var id: Int = -1
var days: List<Week>? = nil
}
class Plan: Object {
var weekDays = List<String>()
var weeks: List<Week>? = nil
}
Mapping code:
let json: [String: Any] = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
let plans: [Plan] = Mapper<Plan>().mapArray(JSONArray: json["plans"] as! [[String: Any]])
let days: [Day] = Mapper<Day>().mapArray(JSONArray: json["days"] as! [[String: Any]])
let weeks: [Week] = Mapper<Week>().mapArray(JSONArray: json["weeks"] as! [[String: Any]])
So, I need to tell realm that an array weeks belong to plan.weeks and an array days belong to object week.days and related by theirs id’s. How can I do this more simply? Do you have any ideas?
The alternative solution is in-head brute force like this.
for week in weeks {
for dayId in week.dayIds {
for day in days {
if day.id == dayId {
week.days.append(day)
}
}
}
}
for plan in plans {
for week in weeks {
for weekId in plans.weekIds {
if weekId == week.id {
plan.weeks.append(week)
}
}
}
}
I believe that somewhere exist more pure and simple solution :)
Thanks.
Your data structures seem very nested, so you're going to have to do the internal looping. If you want something more swifty, use map and filter here instead of for loops:
let days = weeks.map({
$0.dayIds.map({
$0.filter({
$0.id == dayId
})
})
})
JSON
{
"method" : 105,
"values" : {
"item_image" : "A123",
"name" : "Image1",
"description" : "Sample Description"
},
"columns" : {
"name" : {
"type" : "text",
"source" : "name"
},
"item_image" : {
"type" : "image",
"source" : "item_image"
},
"description" : {
"type" : "text",
"source" : "description"
}
}
}
Model
struct Params:Decodable {
let values: Values
let columns: [String: Columns]
}
struct Columns: Decodable {
let source: String
let type: String
}
struct Values: Decodable {
let name: String
let item_image: String
let description: String
}
Code
for (_, val) in param.columns {
colType = val.type
colSource += ", \(val.source)"
let s = "param.values.\(val.source)" \\this line is invalid
let vals = s
print("value: ",vals)
}
Hi! Im working with the lines of code above, and I'm having a problem to call my struct. Instead of using static values i want it dynamic with the code below but its invalid:
let s = "param.values.\(val.source)"
invalid because when it comes to this line:
let vals = s
the result is param.values.name or param.values.item_image, my expected value is Image1 or A123.
to make it clear here's the expected value from the codes and how can I achieve this:
for (_, val) in param.columns {
colType = val.type! expectedvalue= text
colSource += ", \(val.source)" expectedvalue= ", name, item_image, description"
let s = "param.values.\(val.source)" expectedvalue: "param.values.name" or "param.values.item_image"
let vals = s expectedvalue: Image1 or A123
print("value: ",vals) expectedvalue: value: Image1
}
Thanks!
I don't think you can access a property by string. One solution could be to create a mapper function that actually handles that. Still, this assumes that you have pre-determined keys.
struct Params:Decodable {
let values: Values
let columns: [String: Columns]
func getValues(from columns: Columns) -> String? {
switch columns.source {
case "item_image":
return values.item_image
case "name":
return values.name
case "description":
return values.description
default:()
}
if columns.source == "item_image" {
return values.item_image
}
return nil
}
}
struct Columns: Decodable {
let source: String
let type: String
}
struct Values: Decodable {
let name: String
let item_image: String
let description: String
}
let column = Columns(source: "name", type: "text")
let column2 = Columns(source: "item_image", type: "image")
let column3 = Columns(source: "description", type: "text")
let values = Values(name: "Image1", item_image: "A123", description: "Sample Description")
let params = Params(values: values, columns: ["name": column, "item_image": column2, "description": column3])
let name = params.getValues(from: column) //Image1
let itemImage = params.getValues(from: column2) //A123
let description = params.getValues(from: column3) //Sample Description
I am trying to do some complex filtering in my app and I am to a point where I don't know what to do next. My data consistes of an array of dictionaries where the values in each of the dictionaries can be String, Int or [String].
let person1: [String : Any] = ["first_name" : "John",
"last_name" : "Smith",
"age" : 21,
"skills" : ["C#", "Java", "Swift"]]
let person2: [String : Any] = ["first_name" : "Kim",
"last_name" : "Smith",
"age" : 28,
"skills" : ["Java", "Swift"]]
let person3: [String : Any] = ["first_name" : "Kate",
"last_name" : "Bell",
"age" : 24,
"skills" : ["C#"]]
var people = [person1, person2, person3]
I let the user choose how to filter this data and create a dictionary of filter criteria. This dictionary can have any number of keys and values.
let filters: [String : [Any]] = ["age" : [28, 24],
"skills" : ["Java", "Swift"]]
In this example I want to show persons who are age 28 or 24 and have a skills of Java or Swift, which would be person2
Here is what I have so far but it only works with Int values:
for (key, values) in filters {
var filteredItems = people.filter {
var match = false
for filterValue in values {
if $0[key] as! Int == filterValue as! Int {
match = true
break
}
else {
match = false
}
}
return match
}
people = filteredItems
}
Here's how I would do this:
struct Person {
let firstName: String
let lastName: String
let age: Int
let skills: [String]
enum Filter {
enum FilterType<T: Hashable> {
case one(of: [T])
case all(of: [T])
// Match against a property that's a single value
func matches(_ value: T) -> Bool {
switch self {
case .one(let filterValues): return filterValues.contains(value)
case .all(let filterValues): return filterValues.count == 1 && filterValues[0] == value
}
}
// Match against a property that's a list of values
func matches(_ values: [T]) -> Bool {
switch self {
case .one(let filterValues): return !Set(filterValues).intersection(values).isEmpty
case .all(let filterValues): return Set(filterValues).isSuperset(of: values)
}
}
}
case age(is: FilterType<Int>)
case skills(is: FilterType<String>)
func matches(_ p: Person) -> Bool {
switch self {
case .age(let filterValues): return filterValues.matches(p.age)
case .skills(let filterValues): return filterValues.matches(p.skills)
}
}
}
}
extension Array where Element == Person.Filter {
func atLeastOneMatch(_ p: Person) -> Bool {
self.contains(where: { $0.matches(p) })
}
func matchesAll(_ p: Person) -> Bool {
self.allSatisfy { $0.matches(p) }
}
}
let people = [
Person(
firstName: "John",
lastName : "Smith",
age: 21,
skills: ["C#", "Java", "Swift"]
),
Person(
firstName: "Kim",
lastName : "Smith",
age: 28,
skills: ["Java", "Swift"]
),
Person(
firstName: "Kate",
lastName: "Bell",
age: 24,
skills: ["C#"]
),
]
let filters: [Person.Filter] = [
.age(is: .one(of: [28, 24])),
.skills(is: .one(of: ["Java", "Swift"])),
]
let peopleWhoMatchAllFilters = people.filter(filters.matchesAll)
print(peopleWhoMatchAllFilters)
let peopleWhoMatchAtLeastOneFilter = people.filter(filters.atLeastOneMatch)
print(peopleWhoMatchAtLeastOneFilter)
I've extended the filtering capability to be able to specify wether all values of a filter should be matched (e.g. a person must know Java AND Swift AND C#) or at least one (e.g. a person must know AT LEAST Java OR Swift OR C#)