How do you pass data dynamically is a Swift array? - swift

Im creating a tinder like swipe app and I need a new CardData(name: "", age: "") to be created depending on how many profiles I pass through from my database. The number of cards will change. I need the number of cards created to match the the value of the results default. I have looked for the solution for a while and can't find it anywhere.
import UIKit
var nameArrayDefault = UserDefaults.standard.string(forKey: "nameArray")!
var ageArrayDefault = UserDefaults.standard.string(forKey: "ageArray")!
var nameArray = nameArrayDefault.components(separatedBy: ",")
var ageArray = ageArrayDefault.components(separatedBy: ",")
var results = UserDefaults.standard.string(forKey: "results")!
struct CardData: Identifiable {
let id = UUID()
let name: String
let age: String
static var data: [CardData] {[
CardData(name: “\(nameArray[0])”, age: “\(ageArray[0])”),
CardData(name: “\(nameArray[1])”, age: “\(ageArray[1])"),
CardData(name: “\(nameArray[2])”, age: “\(ageArray[2])”)
]}
}

You should initiate the array of CardData objects only the first time and update it after that. You can do the following
var _data = [CardData]()
var data: [CardData] {
if _data.isEmpty() {
self.initiateData()
}
return _data
}
// Initiate data for the first time only
func initiateData() -> [CardData] {
// Check that nameArray has the same size as ageArray
// If the size is different then the data are not valid.
guard nameArray.count == ageArray.count else {
return []
}
// For each of names inside nameArray create the corresponding CardData object
self.nameArray.forEach({ (index, item)
self._data.append(CardData(name: item, age: ageArray[index]))
})
}

Related

delete element from cloud firestore array in swiftui

I'm trying to remove an element (only one) from an array using arrayRemove() but all elements in my "note" array are removed
Note struct:
struct Notes: Identifiable {
var id = UUID().uuidString
var nom: [String]
}
Category struct:
struct Categorys: Identifiable {
var id = UUID().uuidString
var idDoc: String
var note: Notes
}
viewModel:
class DataManager: ObservableObject {
#Published var note: [Notes] = []
#Published var cat: [Categorys] = []
func deleteFields2(cat: Categorys) {
let db = Firestore.firestore()
guard let userID = Auth.auth().currentUser?.uid else {return}
let note = cat.note.nom
db.collection("Users").document(userID).collection("Categorys").document(cat.idDoc).updateData([
"note": FieldValue.arrayRemove(note)
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
To remove an item from the note array, you need to pass the exact value of that array item to arrayRemove. Your code passes an array to arrayRemove, so the code will remove any array item that is an array with the value you specify - which doesn't exist in the screenshots.
To remove the string item from the array, pass a string value to arrayRemove. For example:
db.collection("Users").document(userID).collection("Categorys").document(cat.idDoc).updateData([
"note": FieldValue.arrayRemove("cheese")
])

How to add custom data instances to an array in Firestore?

I have a question where and how to properly save users who are logged into a rooms, now I save them inside the collection "rooms" but I save the user as a reference
func addUserToRoom(room: String, id: String) {
COLLETCTION_ROOMS.document(room).updateData(["members": FieldValue.arrayUnion([COLLETCTION_USERS.document(id)])])
}
But when I create a rooms, I need to pass in an array of users
func fetchRooms() {
Firestore.firestore()
.collection("rooms")
.addSnapshotListener { snapshot, _ in
guard let rooms = snapshot?.documents else { return }
self.rooms = rooms.map({ (queryDocumentSnapshot) -> VoiceRoom in
let data = queryDocumentSnapshot.data()
let query = queryDocumentSnapshot.get("members") as? [DocumentReference]
// ======== I'm making a request here, but I don't understand how to save users next
query.map { result in
result.map { user in
let user = user.getDocument { userSnapshot, _ in
let data = userSnapshot?.data()
guard let user = data else { return }
let id = user["id"] as? String ?? ""
let first_name = user["first_name"] as? String ?? ""
let last_name = user["last_name"] as? String ?? ""
let profile = Profile(id: id,
first_name: first_name,
last_name: last_name)
}
}
}
let id = data["id"] as? String ?? ""
let title = data["title"] as? String ?? ""
let description = data["description"] as? String ?? ""
let members = [Profile(id: "1", first_name: "Test1", last_name: "Test1")]
return VoiceRoom(id: id,
title: title,
description: description,
members: members
})
}
}
This is how my room and profile model looks like
struct VoiceRoom: Identifiable, Decodable {
var id: String
var title: String
var description: String
var members: [Profile]?
}
struct Profile: Identifiable, Decodable {
var id: String
var first_name: String?
var last_name: String?
}
Maybe someone can tell me if I am not saving users correctly and I need to do it in a separate collection, so far I have not found a solution, I would be grateful for any advice.
I feel like this answer is going to blow your mind:
struct VoiceRoom: Identifiable, Codable {
var id: String
var title: String
var description: String
var members: [Profile]?
}
struct Profile: Identifiable, Codable {
var id: String
var first_name: String?
var last_name: String?
}
final class RoomRepository: ObservableObject {
#Published var rooms: [VoiceRoom] = []
private let db = Firestore.firestore()
private var listener: ListenerRegistration?
func addUserToRoom(room: VoiceRoom, user: Profile) {
let docRef = COLLECTION_ROOMS.document(room.id)
let userData = try! Firestore.Encoder().encode(user)
docRef.updateData(["members" : FieldValue.arrayUnion([userData])])
}
func fetchRooms() {
listener = db.collection("rooms").addSnapshotListener { snapshot, _ in
guard let roomDocuments = snapshot?.documents else { return }
self.rooms = roomDocuments.compactMap { try? $0.data(as: VoiceRoom.self) }
}
}
}
And that's it. This is all it takes to correctly store members of a room and simply decode a stored room into an instance of VoiceRoom. All of it is pretty self explanatory but if you have any questions feel free to ask it in the comments.
P.S. I changed COLLETCTION_ROOMS to COLLECTION_ROOMS
Edit:
I decided to elaborate on my changes anyway so that people who just started coding can understand how I reduced the code to just a few lines (skip this if you understood my code).
When your custom data instances conform to the Codable protocol it allows those instances to be automatically transformed into data the database can store (known as Encoding) AND allows them to converted back into the concrete types in your code when you retrieve them from the database (known as Decoding). And the magic of it is all you need to do is add : Codable to your structs and classes to get all this functionality for free!
Now, what about the functions? The addUserToRoom(room:user:) function takes in a VoiceRoom instead of a String because it's generally easier for the caller to just pass in room instead of room.id. This extra step can be done by the function itself and saves a little bit of clutter in your Views while also making it easier.
Finally the fetchRooms() function attaches a listener to the "rooms" collection and fires a block of code any time the collection changes in any way. In the block of code, I check if the document snapshot actually contains documents by using guard let roomDocuments = snapshot?.documents else { return }. After I know I have the documents all that's left to do is convert those documents back into VoiceRoom instances which can easily be done because VoiceRoom conforms to Codable (Remember how I said: "AND allows them to converted back into the concrete types in your code when you retrieve them from the database"). I do this by mapping the array of [QueryDocumentSnapshot] i.e. roomDocuments, into concrete VoiceRoom instances. I use a compact map because if any of the documents fail to decode it won't be contained in self.rooms.
So
try? $0.data(as: VoiceRoom.self)
tries to take every document (represented by $0) and represent its "data" "as" "VoiceRoom" instances.
And that's all it takes!

Inner filtering of array doesn't filter swift

I am trying to filter an array of structs that has array. Below are the data structures I am using. I want the inner array filtered also but it doesn't work
var objects = [SomeObject]() //array of objects
var filteredObject = [SomeObject]() //filtered array
var isSearching = false
struct SomeObject {
var sectionName: String
var sectionObjects : [History]
}
struct History {
var firstName: String
var lastName: Int
}
func searchBar(_ text: String) {
filteredObject = objects.filter({ (obj: SomeObject) -> Bool in
return obj.sectionObjects.filter { $0.firstName.contains(text.lowercased())}.isEmpty
})
print("====", filteredObject, "fill===")
}
let history = History(firstName: "John", lastName: 1)
let anotherHistroy = History(firstName: "Dee", lastName: 2)
let historyArray = [history, anotherHistroy]
let newObject = SomeObject(sectionName: "Section 1", sectionObjects: historyArray)
objects.append(newObject)
searchBar("Jo") // printing of filtered object should not have another history in it's sectionObjects
You might be looking for something like this:
func searchBar(_ text: String) {
filteredObject = []
for var ob in objects {
ob.sectionObjects = ob.sectionObjects.filter {
$0.firstName.contains(text)
}
if !ob.sectionObjects.isEmpty {
filteredObject.append(ob)
}
}
print("====", filteredObject, "fill===")
}
Could perhaps be done more elegantly with reduce(into:), but on the whole it is best to start simply by saying exactly what you mean. You can tweak as desired to take account of case sensitivity.

Update an existing item in a ChatModel dictionary from a Firestore snapshot

I want to show an overview in a TableView of all existing chats (Name, LastMessage) for a specific user.
Right now, I append a new item in my TableView, which is obviously wrong, instead I want to update the existing item by it's key "ChatId"
This is my Model:
class ChatModel {
var chatOwnerId: String?
var chatPartnerId: String?
var createdDate: Timestamp?
var chatId: String?
var lastMessage: String?
var lastMessageDate: Timestamp?
init(dictionary: [String: Any]) {
chatOwnerId = dictionary["chatOwnerId"] as? String
chatPartnerId = dictionary["chatPartnerId"] as? String
createdDate = dictionary["createdDate"] as? Timestamp
chatId = dictionary["chatId"] as? String
lastMessage = dictionary["lastMessage"] as? String
lastMessageDate = dictionary["lastMessageDate"] as? Timestamp
}
}
How I add the data to my model:
func loadChatOverviewNew(currentUser: String) {
ChatApi.shared.observeChatOverviewNew(currentUser: currentUser) { (chatOverview) in
self.chats.insert(chatOverview, at: 0)
self.chatTableView.reloadData()
}
}
How can I update my "chats" when I receive a new snapshot, instead of appending / inserting it?
To update an existing item, find that item in the array by calling ``, and then update it.
Something like:
ChatApi.shared.observeChatOverviewNew(currentUser: currentUser) { (chatOverview) in
if let index = self.chats.firstIndex(where: { $0.chatId == chatOverview.chatId }) {
self.chats[index] = chatOverview
}
else {
self.chats.insert(chatOverview, at: 0)
}
self.chatTableView.reloadData()
}

Multiple Realm objects to JSON

I am trying to convert Realm Object into JSON. My version is working but not if you want to put multiple objects into JSON. So my question is, how should you add multiple Realm Objects into JSON?
Something like that:
{
"Users": [
{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}
],
"Posts": [
{"id": "1","title": "hey"},{"id": "2","title": "hey2"},{"id": "3","title": "hey3"}
]
}
This is what I am doing right now:
func getRealmJSON(name: String, realmObject: Object, realmType: Any) -> String {
do {
let realm = try Realm()
let table = realm.objects(realmType as! Object.Type)
if table.count == 0 {return "Empty Table"}
let mirrored_object = Mirror(reflecting: realmObject)
var properties = [String]()
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String! {
properties.append(property_name)
}
}
var jsonObject = "{\"\(name)\": ["
for i in 1...table.count {
var str = "{"
var insideStr = String()
for property in properties {
let filteredTable = table.value(forKey: property) as! [Any]
insideStr += "\"\(property)\": \"\(filteredTable[i - 1])\","
}
let index = insideStr.characters.index(insideStr.startIndex, offsetBy: (insideStr.count - 2))
insideStr = String(insideStr[...index])
str += "\(insideStr)},"
jsonObject.append(str)
}
let index = jsonObject.characters.index(jsonObject.startIndex, offsetBy: (jsonObject.count - 2))
jsonObject = "\(String(jsonObject[...index]))]}"
return jsonObject
}catch let error { print("\(error)") }
return "Problem reading Realm"
}
Above function does like that, which is good for only one object:
{"Users": [{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}]}
Like this I call it out:
let users = getRealmJSON(name: "Users", realmObject: Users(), realmType: Users.self)
let posts = getRealmJSON(name: "Posts", realmObject: Posts(), realmType: Posts.self)
And I tried to attach them.
Can anybody please lead me to the right track?
You can use data models to encode/decode your db data:
For example you have
class UserEntity: Object {
#objc dynamic var id: String = ""
#objc dynamic var createdAt: Date = Date()
#objc private dynamic var addressEntities = List<AddressEntity>()
var addresses: [Address] {
get {
return addressEntities.map { Address(entity: $0) }
}
set {
addressEntities.removeAll()
let newEntities = newValue.map { AddressEntity(address: $0) }
addressEntities.append(objectsIn: newEntities)
}
}
}
Here you hide addressEntities with private and declare addresses var with Address struct type to map entities into proper values;
And then use
struct User: Codable {
let id: String
let createdAt: Date
let addresses: [Address]
}
And then encode User struct any way you want