Swift: Downcasting a Binding within a List while retaining link - swift

I have the following view code (unworking):
import SwiftUI
struct SearchFilterView: View {
#Binding var filters: [any SourceFilter]
var body: some View {
List($filters, id: \.id) { filter in
switch filter.wrappedValue {
case var textFilter as SourceTextFilter:
TextField(
textFilter.name,
text: Binding(get: { textFilter.value }, set: { textFilter.value = $0 })
)
case var toggleFilter as SourceToggleFilter:
Toggle(
toggleFilter.name,
isOn: Binding(get: { toggleFilter.value }, set: { toggleFilter.value = $0 })
)
case var segmentFilter as SourceSegmentFilter:
Picker(
segmentFilter.name,
selection: Binding(get: { segmentFilter.value }, set: { segmentFilter.value = $0 })
) {
ForEach(segmentFilter.selections, id: \.self) { selection in
Text(selection).tag(selection)
}
}.pickerStyle(.segmented)
default:
EmptyView()
}
}
}
}
The code above compiles, but even though the values change for each filter, as soon as the view reloads, all of the changes are lost. The code for the SourceFilters are below.
protocol JSObjectEncodable {
var object: [String: Any] { get }
}
protocol JSObjectDecodable {
init?(from object: [String: Any])
}
protocol JSObjectCodable: JSObjectDecodable, JSObjectEncodable {}
protocol SourceFilter<ValueType>: JSObjectCodable, Sendable {
associatedtype ValueType
var id: String { get }
var value: ValueType { get set }
var name: String { get }
}
struct SourceTextFilter: SourceFilter {
init?(from object: [String: Any]) {
guard let id = object["id"] as? String,
let value = object["value"] as? String,
let name = object["name"] as? String else { return nil }
self.id = id
self.value = value
self.name = name
}
let id: String
var value: String
let name: String
var object: [String: Any] {
[
"id": id,
"value": value,
"name": name
]
}
}
struct SourceToggleFilter: SourceFilter {
init?(from object: [String: Any]) {
guard let id = object["id"] as? String,
let value = object["value"] as? Bool,
let name = object["name"] as? String else { return nil }
self.id = id
self.value = value
self.name = name
}
let id: String
var value: Bool
let name: String
var object: [String: Any] {
[
"id": id,
"value": value,
"name": name
]
}
}
struct SourceSegmentFilter: SourceFilter {
init?(from object: [String: Any]) {
guard let id = object["id"] as? String,
let value = object["value"] as? String,
let name = object["name"] as? String,
let selections = object["selections"] as? [String] else { return nil }
self.id = id
self.value = value
self.name = name
self.selections = selections
}
let id: String
var value: String
let name: String
let selections: [String]
var object: [String: Any] {
[
"id": id,
"value": value,
"name": name,
"selections": selections
]
}
}
How do I have it so that my changes to the filter values are saved, even after being downcast to their respective filter type? Thanks in advance.

Related

How can I fetch data from a Firestore reference?

I have the chat collection that has the following fields: hasUnreadMessage as a Bool, isActive as a Bool, person as a reference to
person collection, messages as an array of references to message collection.
Here are some screenshots
I want to create a function to fetch all the messages but for example in the person reference when I print directly the call of the imgString or name it's correct, but when I add them to the ChatModel they are missing.
Here is the function that I created.
func fetchMessages() {
db.collection("chat").getDocuments { snapshot, err in
if let error = err {
debugPrint("Error fething documents: \(error)")
} else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
var chatMessages: [Message] = []
var chat: ChatModel = ChatModel(person: Person(name: "", imgString: ""), messages: [], hasUnreadMessage: false, isActive: false)
let personRef = (document.get("person") as? DocumentReference ?? nil)?.getDocument(completion: { personSnapshot, personErr in
if let personError = personErr {
debugPrint("Error getting chat person: \(personError)")
} else {
guard let personSnap = personSnapshot else { return }
let personData = personSnap.data()
chat.person.imgString = personData?["imgString"] as? String ?? ""
chat.person.name = personData?["name"] as? String ?? ""
}
})
let messages = document.get("messages") as? [DocumentReference] ?? []
for message in messages {
message.getDocument { messageSnapshot, messageErr in
if let messageError = messageErr {
debugPrint("Error getting message: \(messageError)")
} else {
guard let messageSnap = messageSnapshot else { return }
let messageData = messageSnap.data()
let date = messageData?["date"] as? String ?? ""
let text = messageData?["text"] as? String ?? ""
let type = messageData?["type"] as? String ?? ""
chatMessages.append(Message(text, type: type, date: date))
}
}
}
chat.hasUnreadMessage = data["hasUnreadMessage"] as? Bool ?? false
chat.isActive = data["isActive"] as? Bool ?? false
chat.messages = chatMessages
self.chats.append(chat)
}
}
}
}
Edit:
Here are the outputs. I saw that I called the imgString print before the ChatModel print and they appeared in the opposite order
BT_Tech.ChatModel(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("D195D8D8-F401-4C71-B571-4877E6574B68")), person: BT_Tech.Person(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("46768262-EB88-42B3-A9FF-4F9FF3A7B7F1")), name: "", imgString: ""), messages: [], hasUnreadMessage: true, isActive: true)
"imgString: girl1"
Here is the ChatModel
struct ChatModel: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
var person: Person
var messages: [Message]
var hasUnreadMessage: Bool
var isActive: Bool
}
struct Person: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
var name: String
var imgString: String
}
struct Message: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
var date: String
var text: String
var type: String
init(_ text: String, type: String, date: String) {
self.text = text
self.type = type
self.date = date
}
init(_ text: String, type: String) {
self.init(text, type: type, date: "")
}
}

Firebase - How do I read this map via embedded structs?

I am reading data from Firestore to be able to populate into expanding tableview cells. I have a really simple struct:
protocol PlanSerializable {
init?(dictionary:[String:Any])
}
struct Plan{
var menuItemName: String
var menuItemQuantity: Int
var menuItemPrice: Double
var dictionary: [String: Any] {
return [
"menuItemName": menuItemName,
"menuItemQuantity": menuItemQuantity,
"menuItemPrice": menuItemPrice
]
}
}
extension Plan : PlanSerializable {
init?(dictionary: [String : Any]) {
guard let menuItemName = dictionary["menuItemName"] as? String,
let menuItemQuantity = dictionary["menuItemQuantity"] as? Int,
let menuItemPrice = dictionary["menuItemPrice"] as? Double
else { return nil }
self.init(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
}
}
And this is embedded in this struct:
protocol ComplainSerializable {
init?(dictionary:[String:Any])
}
struct Complain{
var status: Bool
var header: String
var message: String
var timeStamp: Timestamp
var email: String
var planDetails: Plan
var dictionary: [String: Any] {
return [
"status": status,
"E-mail": header,
"Message": message,
"Time_Stamp": timeStamp,
"User_Email": email,
"planDetails": planDetails
]
}
}
extension Complain : ComplainSerializable {
init?(dictionary: [String : Any]) {
guard let status = dictionary["status"] as? Bool,
let header = dictionary["E-mail"] as? String,
let message = dictionary["Message"] as? String,
let timeStamp = dictionary["Time_Stamp"] as? Timestamp,
let email = dictionary["User_Email"] as? String,
let planDetails = dictionary["planDetails"] as? Plan
else { return nil }
self.init(status: status, header: header, message: message, timeStamp: timeStamp, email: email, planDetails: planDetails)
}
}
However, I am not able to query any data from Firestore which looks like this:
Here is my query, although I am just reading all the files:
let db = Firestore.firestore()
var messageArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
self.messageArray = documentSnapshot!.documents.compactMap({Complain(dictionary: $0.data())})
for plan in self.messageArray {
print("\(plan.email)")
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
What am I doing wrong?
EDIT:
As suggested, here is the updated embedded struct:
import Foundation
// MARK: - Complain
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
Using quicktype.io, you can generate the struct. From there, all you need to do is run this tiny fragment of code within your response handler.
var compainArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
guard let snapshot = documentSnapshot else {return}
for document in snapshot.documents {
if let jsonData = try? JSONSerialization.data(withJSONObject: document.data()){
if let converted = try? JSONDecoder().decode(Complain.self, from: jsonData){
self.compainArray.append(converted)
}
}
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
Which will handle the looping, and mapping of certain variables. Let me know if you have any trouble with this.

How to save complicated object in Firebase?

I have 2 classes. A class User which contains array of objects of class Item. I want to save object of User in Firebase. What do I need to do for it?
class User: NSObject {
var name: String
var items: [Item]
init(dictionary: [String: Any]) {
self.name = dictionary["name"] as? String ?? ""
}
}
class Item{
var name: String
init(name: String) {
self.name = name
}
}
var user = User(dictionary: [:])
user.name = "Tom"
var item = Item(name: "item1")
user.items.append(item)
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("users").child(uid).setValue(user)
Save them as firebase object.
class User: NSObject {
var name: String
var items: [Item]
init(dictionary: [String: Any]) {
self.name = dictionary["name"] as? String ?? ""
}
var json: [String: Any] {
return ["name": name, "items": items.map { $0.json }]
}
}
class Item{
var name: String
init(name: String) {
self.name = name
}
var json: [String: Any] {
return ["name": name]
}
}
Database.database().reference().child("users").child(uid).setValue(user.json)

Swift - How to init an array in struct?

I'm not sure how to init the array in Struct. I'm not able to fetch data from array, meanwhile I was manage to get the result from object (platform.status).
Am I init it wrongly ?
Any ideas ?
Here is Network Request :
func fetchServicePlatform(token: String, _key: String) {
let selectedUrl = URL(string: "\(mainUrl)/get_service")
let parameters: [String: String] = ["_key": _key]
var serviceList = [ServiceList]()
URLSession.shared.dataTask(with: setupURLRequest(selectedURL: selectedUrl!, parameters: parameters, token: token, email: "test#gmail.com")) { (data, response, error) in
if let err = error {
print("Failed to fetch API: \(err.localizedDescription)")
}
guard let data = data else { return }
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else { return }
let platform = Platform(json: json)
if platform.status == "success" {
self.serviceList = platform.service_list
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
} catch let jsonErr {
print("Failed to fetch service platform: ", jsonErr.localizedDescription)
}
}.resume()
}
Here is JSON :
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
Here is Struct :
struct Platform {
let service_list: [ServiceList]
let status: String
init(json: [String: Any]) {
service_list = [ServiceList(json: json["service_list"] as? [String: Any] ?? [:])]
status = json["status"] as? String ?? ""
}
}
struct ServiceList {
let title: String
let description: String
let image: String
init(json: [String: Any]) {
title = json["title"] as? String ?? ""
description = json["description"] as? String ?? ""
image = json["image"] as? String ?? ""
}
}
In your data json["service_list"] is an array of dictionaries,
You Can try out.
struct Platform {
var service_list: [ServiceList] = []
var status: String
init(json: [String: Any]) {
if let jsonArray = json["service_list"] as? [[String: Any]] {
for service in jsonArray {
service_list.append(ServiceList(json: service))
}
}
else{
service_list = []
}
status = json["status"] as? String ?? ""
}
}
init an array in the struct
struct MyData {
var dataArray:[Any] = []
var latitude: Float
var longitude: Float
}
You have to unwrap the dictionary as an array of dictionaries and then loop through it with a map or flatmap, where you use the value $0 as the value
guard let serviceList = (json["service_list"] as? [[String: Any]])?.flatmap({ServiceList(withDictionary: $0)})
Go with the below approach, this way it is simpler
Create your structure with Codable
struct Platform: Codable {
let service_list: [ServiceList]
let status: String
enum CodingKeys: String, CodingKey {
case service_list, status
}
}
struct ServiceList: Codable {
let title: String
let description: String
let image: String
enum CodingKeys: String, CodingKey {
case title, description, image
}
}
Your json data object
let json = """
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
""".data(using: .utf8)!
Use the JSONDecoder to map the json object to structure
let decoder = JSONDecoder()
let platform = try! decoder.decode(Platform.self, from: json)
if platform.status == "pass"{
for service in platform.service_list{
print(service.title)
}
}

groupBy array by two values in swift

I'm using this extension of Array to groupBy:
func groupBy <U> (groupingFunction group: (Element) -> U) -> [U: Array] {
var result = [U: Array]()
for item in self {
let groupKey = group(item)
// If element has already been added to dictionary, append to it. If not, create one.
if result.has(groupKey) {
result[groupKey]! += [item]
} else {
result[groupKey] = [item]
}
}
return result
}
And trying to make a dictionary from teamId and rank (custom properties of my object from array).
self.items = myArray.groupBy {
team in
if team.rank == nil {
return team.teamId!
} else {
return team.rank!
}
}
My case looks something like this:
[{
"team_id": "4",
"team_name": "T16",
"rank": "3"
},
,{
"team_id": "4",
"team_name": "T16",
"rank": "2"
},
{
"team_id": "4",
"team_name": "T16",
"rank": "1"
}
,{
"team_id": "5",
"team_name": "T16",
"rank": null
}]
desired output:
let teams : [String: TeamItem] = [ "4" : TeamItem.rank3, "4-2" : TeamItem.rank2, "4-1" : TeamItem.rank1, "5" : TeamItem]
EDIT 2
func requestTeamData(listener:([TeamItem]) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let baseURL: String = self._appSettings.getPathByName(EndPoint.BASE_URL.rawValue) as! String
let paths: NSDictionary = self._appSettings.getPathByName(EndPoint.PATH.NAME.rawValue) as! NSDictionary
let teamPaths: NSDictionary = paths.objectForKey(EndPoint.PATH.TEAM.KEY.rawValue) as! NSDictionary
let path = teamPaths.valueForKey(EndPoint.PATH.TEAM.PATH.rawValue) as! String
request(.GET, baseURL + path, parameters:[:])
.responseCollection { (_, _, team: [TeamItem]?, error) in
if let team = team {
dispatch_async(dispatch_get_main_queue()) {
listener(team)
}
}
}
}
}
Calling the API, straightforward
DataProvider.getInstance().requestTeamData({(items:[TeamItem]) -> () in
//Getting the items from the API
})
EDIT 1
#objc final class TeamItem: Equatable, ResponseObjectSerializable, ResponseDictionarySerializable {
let firstName: String?
let lastName: String?
let seasonId: String?
let seasonName: String?
let teamId: String?
let teamName: String?
let rank: String?
let positionId: String?
let positionName: String?
let number: String?
let birthDate: String?
let birthPlace: String?
let citizenship: String?
let height: String?
let weight: String?
let maritalStatus: String?
let model: String?
let hobby: String?
let food: String?
let book: String?
let movie: String?
let wish: String?
let message: String?
let biography: String?
let imageURL: String?
let teamImageURL: String?
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.firstName = representation.valueForKeyPath(EndPoint.Keys.Team.FirstName.rawValue) as? String
self.lastName = representation.valueForKeyPath(EndPoint.Keys.Team.LastName.rawValue) as? String
self.seasonId = representation.valueForKeyPath(EndPoint.Keys.Team.SeasonId.rawValue) as? String
self.seasonName = representation.valueForKeyPath(EndPoint.Keys.Team.SeasonName.rawValue) as? String
self.teamId = representation.valueForKeyPath(EndPoint.Keys.Team.TeamId.rawValue) as? String
self.teamName = (representation.valueForKeyPath(EndPoint.Keys.Team.TeamName.rawValue) as? String)!
self.rank = representation.valueForKeyPath(EndPoint.Keys.Team.Rank.rawValue) as? String
self.positionId = representation.valueForKeyPath(EndPoint.Keys.Team.PositionId.rawValue) as? String
self.positionName = representation.valueForKeyPath(EndPoint.Keys.Team.PositionName.rawValue) as? String
self.number = representation.valueForKeyPath(EndPoint.Keys.Team.Number.rawValue) as? String
self.birthDate = representation.valueForKeyPath(EndPoint.Keys.Team.BirthDate.rawValue) as? String
self.birthPlace = (representation.valueForKeyPath(EndPoint.Keys.Team.BirthPlace.rawValue) as? String)!
self.citizenship = representation.valueForKeyPath(EndPoint.Keys.Team.Citizenship.rawValue) as? String
self.height = representation.valueForKeyPath(EndPoint.Keys.Team.Height.rawValue) as? String
self.weight = representation.valueForKeyPath(EndPoint.Keys.Team.Weight.rawValue) as? String
self.maritalStatus = representation.valueForKeyPath(EndPoint.Keys.Team.MaritalStatus.rawValue) as? String
self.model = representation.valueForKeyPath(EndPoint.Keys.Team.Model.rawValue) as? String
self.hobby = representation.valueForKeyPath(EndPoint.Keys.Team.Hobby.rawValue) as? String
self.food = representation.valueForKeyPath(EndPoint.Keys.Team.Food.rawValue) as? String
self.book = representation.valueForKeyPath(EndPoint.Keys.Team.Book.rawValue) as? String
self.movie = representation.valueForKeyPath(EndPoint.Keys.Team.Movie.rawValue) as? String
self.wish = representation.valueForKeyPath(EndPoint.Keys.Team.Wish.rawValue) as? String
self.message = representation.valueForKeyPath(EndPoint.Keys.Team.Message.rawValue) as? String
self.biography = representation.valueForKeyPath(EndPoint.Keys.Team.Biography.rawValue) as? String
self.imageURL = representation.valueForKeyPath(EndPoint.Keys.Team.ImageURL.rawValue) as? String
self.teamImageURL = representation.valueForKeyPath(EndPoint.Keys.Team.TeamImageURL.rawValue) as? String
}
#objc static func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [TeamItem] {
var teamItemArray: [TeamItem] = []
if let representation = representation as? [[String: AnyObject]] {
for teamItemRepresentation in representation {
if let teamItem = TeamItem(response: response, representation: teamItemRepresentation) {
teamItemArray.append(teamItem)
}
}
}
return teamItemArray
}
}
func ==(lhs: TeamItem, rhs: TeamItem) -> Bool {
return lhs.lastName == rhs.lastName
}
}
EDIT
TeamItem.rank1 means actually that the value of the rank is 1 (the same for others)
This is what've need, hope will help someone:
#objc static func dictionary(#response: NSHTTPURLResponse, representation: AnyObject) -> [String: Array<TeamItem>] {
var teamItemDictionary: [String: Array<TeamItem>] = [:]
if let representation = representation as? [[String: AnyObject]] {
for teamItemRepresentation in representation {
if let teamItem = TeamItem(response: response, representation: teamItemRepresentation) {
if teamItem.rank != nil {
let teamKey = teamItem.teamId! + "-" + teamItem.rank!
if teamItem.teamId != EndPoint.Keys.Team.Type.rawValue {
if let team = teamItemDictionary[teamKey] as [TeamItem]? {
teamItemDictionary[teamKey]?.push(teamItem)
} else {
teamItemDictionary[teamKey] = []
teamItemDictionary[teamKey]?.push(teamItem)
}
}
} else {
if teamItem.teamId != EndPoint.Keys.Team.Type.rawValue {
if let team = teamItemDictionary[teamItem.teamId!] as [TeamItem]? {
teamItemDictionary[teamItem.teamId!]?.push(TeamItem)
} else {
teamItemDictionary[teamItem.teamId!] = []
teamItemDictionary[teamItem.teamId!]?.push(TeamItem)
}
}
}
}
}
}
return teamItemDictionary
}