How to make likes updateable without registration? - swift

I made a code that adds likes and shows their number on the screen.
But there is a problem, when you download the application on 2 devices and press the button at the same time, then only one like is counted. How can I fix this without implementing registration?
There is an idea to make fields that will be created for everyone on the phone when the like is pressed and this number will be added to the total, but I do not know how to implement this.
Here's the current code:
struct LikeCounts {
var likecount: String
}
class LikeTextModel: ObservableObject {
#Published var likecounts: LikeCounts!
private var db = Firestore.firestore()
init() {
updateLike()
}
func updateLike() {
db.collection("likes").document("LikeCounter")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
if let likecount = data["likecount"] as? String {
DispatchQueue.main.async {
self.likecounts = LikeCounts(likecount: likecount)
}
}
}
}
#ObservedObject private var likeModel = LikeTextModel()
if self.likeModel.likecounts != nil{
Button(action:
{self.like.toggle()
like ? addlike(): dellike()
UserDefaults.standard.setValue(self.like, forKey: "like")
}) {
Text((Text(self.likeModel.likecounts.likecount))}
func addlike() {
let db = Firestore.firestore()
let like = Int.init(self.likeModel.likecounts.likecount)
db.collection("likes").document("LikeCounter").updateData(["likecount": "\(like! + 1)"]) { (err) in
if err != nil {
print(err)
return
}
}
}
func dellike() {
let db = Firestore.firestore()
let like = Int.init(self.likeModel.likecounts.likecount)
db.collection("likes").document("LikeCounter").updateData(["likecount": "\(like! - 1)"]) { (err) in
if err != nil {
print(err)
return
}
}
}

Firestore has the ability to reliably increment a value, like this:
db.collection('likes').doc('LikeCounter')
.updateData([
"likecount": FieldValue.increment(1)
]);

Related

Matching collections in Firestore using SwiftUI

I'm try to fetch documents from firestore by matching the collections using the code below. I'm fetching the documents from collection("users") in case anyone update their info in the future which the new info will only uploaded to the collection("users") but not the collection("clients").
struct NewFriend: Identifiable, Hashable, Decodable {
#DocumentID var id: String?
var name: String
var uid: String
}
struct NewProspect: Identifiable, Hashable, Decodable {
#DocumentID var id: String?
var name: String
var uid: String
}
class AuthViewModel: ObservableObject {
#Published var userName = ""
#Published var friendName1 = "FriendName1"
#Published var friendName2 = "FriendName2"
#Published var friendUid1 = "friendUid1"
#Published var friendUid2 = "friendUid2"
#Published var matchingUid = ""
let friends = [NewFriend]()
let clients = [NewProspect]()
func uploadInfo() {
guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return }
let data: [String: Any] = [
"name": self.userName,
"uid": uid
]
FireBaseManager.shared.firestore.collection("users").document(uid).setData(data) { error in
if let error = error {
print("DEBUG: Failed to store user info with error: \(error.localizedDescription)")
return
}
}
}
}
func addFriend() {
guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return }
let data: [String: Any] = [
"name": self.friendName1,
"uid": self.friendUid1
]
FireBaseManager.shared.firestore.collection("users").document(uid).collections("clients").document(self.friendUid1).setData(data) { error in
if let error = error {
print("DEBUG: Failed to store user info with error: \(error.localizedDescription)")
return
}
}
func addFriend2() {
guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return }
let data: [String: Any] = [
"name": self.friendName2,
"uid": self.friendUid2
]
FireBaseManager.shared.firestore.collection("users").document(uid).collections("clients").document(self.friendUid2).setData(data) { error in
if let error = error {
print("DEBUG: Failed to store user info with error: \(error.localizedDescription)")
return
}
}
func fetchFriends() {
guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return }
FireBaseManager.shared.firestore.collection("users").document(uid).collection("clients").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
self.friends = documents.compactMap({ try? $0.data(as: NewFriend.self) })
for i in 0 ..< self.friends.count {
self.matchingUid = self.friend[i].uid
print("print1: \(self.matchingUid)")
FireBaseManager.shared.firestore.collection("users")
.whereField("uid", isEqualTo: self.friendUid)
.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
self.clients = documents.compactMap({ try? $0.data(as: NewProspect.self) })
print("print2: \(self.matchingUid)")
}
}
}
}
}
struct ListView: View {
#ObservedObject var viewModel = AuthViewModel()
var body: some View {
List {
ForEach(viewModel.clients) { client in
Text(client.name)
.padding()
}
}
.onAppear {
viewModel.fetchFriends()
}
}
}
I'm able to print both of the friends' uid in print1 but when it comes to print2 only one of the uid is printed and the list only shows one of the two friends. Any idea why is this happening? Or is there any better way to make this function work?
When you fetch users before print2, you are fetching with a where clause.
self.db.collection("users")
// This WHERE condition causes that only one friend will be found
.whereField("uid", isEqualTo: self.friendUid1)
.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
self.clients = documents.compactMap({ try? $0.data(as: NewProspect.self) })
print("print2: \(self.matchingUid)")
}
The one in your example (self.friendUid) is not part of your AuthViewModel, I assume you meant .whereField("uid", isEqualTo: self.friendUid1)
Anyway, that where clause is limiting your results to one of the friends (in this case the friend with friendUid1 only.
Maybe you can use .whereField("uid", in: [self.friendUid1, self.friendUid2]) instead:
self.db.collection("users")
// In this case the WHERE condition cares about both friends
.whereField("uid", in: [self.friendUid1, self.friendUid2])
.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
self.clients = documents.compactMap({ try? $0.data(as: NewProspect.self) })
print("print2: \(self.matchingUid)")
}

Unable to return value from Struct to View in SwiftUI

So I'll keep it simple. I've seen questions with similar error.
Unexpected non-void return value in void function
But I already defined medicalContent as a string so I don't know what's goin wrong. I just wish to pass the return medicalContent string from the struct to the textEditor in the view. Any input shall be appreciated!
Code:
struct MedicalEdit: View {
#State var medicalContent: String = ""
init(){
fetchCalmStuff()
}
func fetchCalmStuff(){
let user = Auth.auth().currentUser
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
let ref = db.collection("list").document(uid).collection("notes")
ref.document("medical").setData(["content":"medical content","identity":"medical" ])
db.collection("userslist").document(uid).collection("Themes").whereField("identity", isEqualTo: "medical")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let data = document.data()
let medicalContent = data["content"] as! String ?? ""
print(medicalContent)
return medicalContent // this is where I get error // that Unexpected non-void return value in void function.
}
}
}
}
var body: some View {
TextEditor(text: $medicalContent)
}
}
EDIT: Also how come it can print medicalContent but cannot return it? I'm kinda confused.

How to grab the current users "firstname" from firebase store. Swift 5

I did more trial and error and a bit of online research and this is what I came back with:
func presentWelcomeMessage() {
//Get specific document from current user
let docRef = Firestore.firestore()
.collection("users")
.whereField("uid", isEqualTo: Auth.auth().currentUser?.uid ?? "")
// Get data
docRef.getDocuments { (querySnapshot, err) in
if let err = err {
print(err.localizedDescription)
} else if querySnapshot!.documents.count != 1 {
print("More than one document or none")
} else {
let document = querySnapshot!.documents.first
let dataDescription = document?.data()
guard let firstname = dataDescription?["firstname"] else { return }
self.welcomeLabel.text = "Hey, \(firstname) welcome!"
}
}
It works, but am not sure if it is the most optimal solution.
First I should say firstname is not really the best way to store a var. I would recommend using firstName instead for readability. I also recommend getting single documents like I am, rather than using a whereField.
An important thing to note is you should create a data model like I have that can hold all of the information you get.
Here is a full structure of how I would get the data, display it, and hold it.
struct UserModel: Identifiable, Codable {
var id: String
var firstName: String
private enum CodingKeys: String, CodingKey {
case id
case firstName
}
}
import SwiftUI
import FirebaseAuth
import FirebaseFirestore
import FirebaseFirestoreSwift
class UserDataManager: ObservableObject {
private lazy var authRef = Auth.auth()
private lazy var userInfoCollection = Firestore.firestore().collection("users")
public func getCurrentUIDData(completion: #escaping (_ currentUserData: UserModel) -> Void) {
if let currentUID = self.authRef.currentUser?.uid {
self.userInfoCollection.document(currentUID).getDocument { (document, error) in
if let document = document {
if let userData = try? document.data(as: UserModel.self) {
completion(userData)
}
} else if let error = error {
print("Error getting current UID data: \(error)")
}
}
} else {
print("No current UID")
}
}
}
struct ContentView: View {
#State private var userData: UserModel? = nil
private let
var body: some View {
ZStack {
if let userData = self.userData { <-- safely unwrap data
Text("Hey, \(userData.firstName) welcome!")
}
}
.onAppear {
if self.userData == nil { <-- onAppear can call more than once
self.udm.getCurrentUIDData { userData in
self.userData = userData <-- pass data from func to view
}
}
}
}
}
Hopefully this can point you in a better direction of how you should be getting and displaying data. Let me know if you have any further questions or issues.

Firestore Swift update text realtime

I have this way of collecting information.
struct MainText {
var mtext: String
var memoji: String
}
class MainTextModel: ObservableObject {
#Published var maintext : MainText!
init() {
updateData()
}
func updateData() {
let db = Firestore.firestore()
db.collection("maintext").document("Main").getDocument { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
let memoji = snap?.get("memoji") as! String
let mtext = snap?.get("mtext") as! String
DispatchQueue.main.async {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}
}
}
And such a way of displaying.
#ObservedObject private var viewModel = MainTextModel()
self.viewModel.maintext.memoji
self.viewModel.maintext.mtext
How can I update online without rebooting the view?
Instead of using getDocument, which only gets the document once and doesn't return updates, you'll want to add a snapshot listener.
Here's the Firestore documentation for that: https://firebase.google.com/docs/firestore/query-data/listen
In your case, you'll want to do something like:
db.collection("maintext").document("Main")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
if let memoji = data["memoji"] as? String, let mtext = data["mtext"] as? String {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}

Filter Firebase Data SwiftUI

This code adds all the data in a single array. In HomeViev I use to Foreach and I added to data to list. But I have to split the data in two. status collection has two type "active" and "closed" but I don't know how can I filter
import SwiftUI
import Combine
import Firebase
let dbCollection = Firestore.firestore().collection("Signals")
class FirebaseSession : ObservableObject {
#Published var session: User? { didSet { self.didChange.send(self) }}
#Published var data = [Signal]()
var didChange = PassthroughSubject<FirebaseSession, Never>()
var handle: AuthStateDidChangeListenerHandle?
func listen () {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("Got user: \(user)")
self.session = User(uid: user.uid, email: user.email)
self.readData()
} else {
self.session = nil
}
}
}
func readData() {
dbCollection.addSnapshotListener { (documentSnapshot, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}else {
print("read data success")
}
documentSnapshot!.documentChanges.forEach { i in
// Read real time created data from server
if i.type == .added {
let id = i.document.documentID
let symbol = i.document.get("symbol") as? String ?? ""
let status = i.document.get("status") as? String ?? ""
self.data.append(Signal(id: id, symbol: symbol, status: status))
}
// Read real time modify data from server
if i.type == .modified {
self.data = self.data.map { (eachData) -> Signal in
var data = eachData
if data.id == i.document.documentID {
data.symbol = i.document.get("symbol") as! String
data.status = i.document.get("status") as? String ?? ""
return data
}else {
return eachData
}
}
}
// When data is removed...
if i.type == .removed {
let id = i.document.documentID
for i in 0..<self.data.count{
if self.data[i].id == id{
self.data.remove(at: i)
return
}
}
}
}
}
}
}
The question states
But I have to split the data in two
I assume that means two arrays; one for active and one for closed.
var activeData = [...
var closedData = [...
There are a couple of ways to do that
1)
Query Firestore for all status fields equal to active and load those documents into the active array and then another query for status fields equal closed and load those in the the closed array
2)
I would suggest a simpler approach
if i.type == .added {
let id = i.document.documentID
let symbol = i.document.get("symbol") as? String ?? ""
let status = i.document.get("status") as? String ?? ""
if status == "active" {
self.activeData.append(Signal(id: id, symbol: symbol, status: status))
} else {
self.closedData.append(Signal(id: id, symbol: symbol, status: status))
}
}
and do the same thing within .modified and .removed; identify the status so the code will know which array to remove it from.
EDIT:
Based on a comment
I don't know how to query this codes.
I am providing code to query for signals that are active. This code will return only active signals and as signals become active, inactive etc, this will modify a signalArray to stay in sync with the data.
let dbCollection = Firestore.firestore().collection("Signals")
let query = dbCollection.whereField("status", isEqualTo: "active").addSnapshotListener( { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let signalToAdd = Signal(withDoc: diff.document)
self.signalArray.append(signalToAdd)
}
if (diff.type == .modified) {
let docId = diff.document.documentID
if let indexOfSignalToModify = self.signalArray.firstIndex(where: { $0.signal_id == docId} ) {
let signalToModify = self.signalArray[indexOfSignalToModify]
signalToModify.updateProperties(withDoc: diff.document)
}
}
if (diff.type == .removed) {
let docId = diff.document.documentID
if let indexOfSignalToRemove = self.signalArray.firstIndex(where: { $0.signal_id == docId} ) {
self.signalArray.remove(at: indexOfSignalToRemove)
}
}
}
})
Note that my Signal Class has an initializer that accepts a QueryDocumentSnapshot to initialize it as well as a .updateProperties function to update its internal properties.