I'm a beginner trying to get the app to be able to post and see posts that are in Firebase. Every time I run this part of the app I get an error and the app crashes. Any help?
The error occurs on this line: let time = doc.document.data()["time"] as! Timestamp
(Thread 1: signal SIGABRT)
This is part of my code,
import SwiftUI
import Firebase
class PostViewModel : ObservableObject{
#Published var posts : [PostModel] = []
#Published var noPosts = false
#Published var newPost = false
#Published var updateId = ""
init() {
getAllPosts()
}
func getAllPosts(){
ref.collection("Posts").addSnapshotListener { (snap, err) in
guard let docs = snap else{
self.noPosts = true
return
}
if docs.documentChanges.isEmpty{
self.noPosts = true
return
}
docs.documentChanges.forEach { (doc) in
// Checking If Doc Added...
if doc.type == .added{
// Retrieving and appending...
let title = doc.document.data()["title"] as! String
let time = doc.document.data()["time"] as! Timestamp
let pic = doc.document.data()["url"] as! String
let userRef = doc.document.data()["ref"] as! DocumentReference
Related
I am trying to find a way to connect my snapshot listener to specific variables, so that the rating average which is shown to the user is updated after each rating is submitted. Right now, I'm able to write new averages with the most recent submission taken into account to by Firestore Database. I can also see that my listener works by printing to the console, and it pulls the value that was just written each time. I'm trying to take what is written to the console and assign it to my variables at the top of the view structure so that each new submission is submitted on top of the previous submission averages.
struct NewRatingView: View {
var schools = ["North Avenue", "West Village", "Brittain"]
#State private var selectedSchool = "West Village"
#State private var userCurrentRating: CGFloat = 0.0
#State private var userUsualRating: CGFloat = 0.0
#State private var isUserRatingCurrent = true
#State private var averageCurrentRating: CGFloat = 2
#State private var isUserRatingUsual = true
#State private var averageUsualRating: CGFloat = 2
#State private var totalRatingsCurrent = 1
#State private var totalRatingsUsual = 1
// body view code
private func storeRatingInformation() {
// guard let uid = Auth.auth().currentUser?.uid else {return}
let id = selectedSchool
let ratingData = ["location": selectedSchool, "currentRating": averageCurrentRating, "usualRating": averageUsualRating, "totalCurrentRatings": totalRatingsCurrent, "totalUsualRatings": totalRatingsUsual] as [String : Any]
let db = Firestore.firestore()
db.collection("RatingInformation").document(id)
.setData(ratingData) { error in
if let error = error {
print(error)
return
}
}
}
private func snapshotListener() {
let id = selectedSchool
let db = Firestore.firestore()
db.collection("RatingInformation").document(id)
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
if let currentRating = document.get("currentRating") as? CGFloat {
averageCurrentRating = currentRating
print("Testing:")
print(averageCurrentRating)
print(document.data())
}
}
}
When I compile I get this error "The path to the document cannot be empty".
To fix this, I should add an unescaped closure.
How to add an unescaped closure to the fetchUsers () function and call the GetCorsodiLaurea () function in the closure? In such a way, the compiler will not try to execute functions asynchronously.
LoginViewModel
import SwiftUI
import Firebase
import LocalAuthentication
class LoginViewModel: ObservableObject {
#Published var email: String = ""
#Published var password: String = ""
#AppStorage("use_face_id") var useFaceID: Bool = false
#AppStorage("use_face_email") var faceIDEmail: String = ""
#AppStorage("use_face_password") var faceIDPassword: String = ""
//Log Status
#AppStorage("log_status") var logStatus: Bool = false
//MARK: Error
#Published var showError: Bool = false
#Published var errorMsg: String = ""
// MARK: Firebase Login
func loginUser(useFaceID: Bool,email: String = "",password: String = "")async throws{
let _ = try await Auth.auth().signIn(withEmail: email != "" ? email : self.email, password: password != "" ? password : self.password)
DispatchQueue.main.async {
if useFaceID && self.faceIDEmail == ""{
self.useFaceID = useFaceID
// MARK: Storing for future face ID Login
self.faceIDEmail = self.email
self.faceIDPassword = self.password
}
self.logStatus = true
}
}
//MARK: FaceID Usage
func getBioMetricStatus()->Bool{
let scanner = LAContext()
return scanner.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: .none)
}
// MARK: FaceID Login
func autenticationUser()async throws{
let status = try await LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "To Login Into App")
if status{
try await loginUser(useFaceID: useFaceID,email: self.faceIDEmail,password: self.faceIDPassword)
}
}
}
ProfileViewModel
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
import SwiftUI
class ProfileViewModel: ObservableObject {
#Published var userInfo: UserModel = .empty
#Published var userDegree: userDegreeModel = .empty
#Published var isSignedIn = false
#Published var showError: Bool = false
#Published var errorMsg: String = ""
var uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
// listen for auth state change and set isSignedIn property accordingly
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
print("Signed in as user \(user.uid).")
self.uid = user.uid
self.isSignedIn = true
}
else {
self.isSignedIn = false
self.userInfo.Nomeintero = ""
}
}
fetchUser() { [self] in
fetchDegrees()
}
}
func fetchUser(completion: #escaping () -> Void) {
let docRef = db.collection("users").document(uid)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMsg = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.userInfo = try document.data(as: UserModel.self)!
completion()
}
catch {
print(error)
}
}
}
}
}
func fetchDegrees() {
let docRef = db.collection("DegreeCourses").document(userInfo.Tipocorso)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMsg = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.userDegree = try document.data(as: userDegreeModel.self)!
}
catch {
print(error)
}
}
}
}
}
}
UserModel
import SwiftUI
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
public struct UserModel: Codable{
#DocumentID var id: String?
var Nome : String
var Cognome : String
var photoURL : String
var Nomeintero : String
var Corsodilaurea : String
var Tipocorso : String
}
extension UserModel {
static let empty = UserModel(Nome: "", Cognome: "", photoURL: "", Nomeintero: "", Corsodilaurea: "", Tipocorso: "")
}
userDegreeModel
import SwiftUI
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
struct userDegreeModel: Codable {
#DocumentID var id: String?
var Name : String
var TotalSubjects : Int
}
extension userDegreeModel {
static let empty = userDegreeModel(Name: "", TotalSubjects: 0)
}
Error
A couple of notes:
Firestore calls return on the main dispatch queue already about this), so you don't need to manually switch to the main queue using DispatchQueue.async { }. See my Twitter thread for more details.
Instead of mapping Firestore documents manually, you can use Codable to do so. This means less code to write, and fewer typos :-) Here is an article that goes into much more detail: Mapping Firestore Data in Swift - The Comprehensive Guide | Peter Friese
Accessing the signed in user using Auth.auth().currentUser!.uid might result in you app breaking if no user is signed in. I recommend implementing an authentication state listener instead.
Since all of Firebase's APIs are asynchronous (see my blog post about this: Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese), the result of fetchUser will take a short moment, so you want to make sure to only call fetchDegrees once that call has completed. One way to do this is to use a completion handler.
Lastly, I recommend following a styleguide like this one for naming your classes and attribute: Swift Style Guide
I've updated your code accordingly below.
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
import SwiftUI
public struct UserModel: Codable {
#DocumentID var id: String?
var firstName: String
var lastName: String
var photoUrl: String
// ...
}
extension UserModel {
static let empty = UserModel(firstName: "", lastName: "", photoUrl: "")
}
public struct UserDegreeModel: Codable {
#DocumentID var id: String?
var name: String
var totalSubjects: Int
// ...
}
extension UserDegreeModel {
static let empty = UserDegreeModel(name: "", totalSubjects: 0)
}
class ProfileViewModel: ObservableObject {
#Published var userInfo: UserModel = .empty
#Published var userDegree: UserDegreeModel = .empty
#Published var isSignedIn = false
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
// listen for auth state change and set isSignedIn property accordingly
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
print("Signed in as user \(user.uid).")
self.uid = user.uid
self.isSignedIn = true
}
else {
self.isSignedIn = false
self.username = ""
}
}
fetchUser() {
fetchDegrees()
}
}
func fetchUser(completion: () -> Void) {
let docRef = db.collection("users").document(uid)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.user = try document.data(as: UserModel.self)
completion()
}
catch {
print(error)
}
}
}
}
}
func fetchDegrees() {
let docRef = db.collection("DegreeCourses").document(userInfo.Tipocorso)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.userDegree = try document.data(as: UserDegreeModel.self)
}
catch {
print(error)
}
}
}
}
}
}
I'm working on a swiftui app that streams movies and it is linked to Firebase. Currently, I'm trying to get the time a video (through AVPlayer) has been paused and update that to my database time field so that users can start where they stopped. I'm trying to update the data using a Codable format and also have imported FirebaseFirestoreSwift. However, the code is not making any changes to the database so was wondering if I can get help. Querying the data from the database works perfectly, I just need guidance on how to update different fields of the movies collection from SwiftUI. Many thanks!
import FirebaseFirestoreSwift
...
#State var SectionX: movies
....
//when video is ended record time and update value to firestore time field
.onDisappear {
avPlayer.pause()
let db = Firestore.firestore()
let currentTime = Float(avPlayer.currentTime().seconds)
print(currentTime)
db.collection("movies").document(SectionX.id)
SectionX.time = String(currentTime)
func updateTime(_ mov: movies) {
do {
let _ = try db.collection("movies").document(SectionX.time).setData(from: currentTime)
}
catch {
print(error)
}
}
}
struct movies : Codable,Identifiable {
var id: String
var title: String
var img: String
var video: String
var description: String
var genre: String
var maturity: String
var time: String
var year: String
var acting: String
var directors: String
var writers: String
var producers: String
var watched: Int
var rating: Int
}
class getMoviesData : ObservableObject{
#Published var datas = [movies]()
init(){
let db = Firestore.firestore()
db.collection("movies").addSnapshotListener{ (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
for i in snap!.documentChanges{
let id = i.document.get("id") as! String
let title = i.document.get("title") as! String
let img = i.document.get("img") as! String
let video = i.document.get("video") as! String
let description = i.document.get("description") as! String
let genre = i.document.get("genre") as! String
let maturity = i.document.get("maturity") as! String
let time = i.document.get("time") as! String
let year = i.document.get("year") as! String
let acting = i.document.get("acting") as! String
let directors = i.document.get("directors") as! String
let writers = i.document.get("writers") as! String
let producers = i.document.get("producers") as! String
let watched = i.document.get("watched") as! Int
let rating = i.document.get("rating") as! Int
self.datas.append(movies(id: id, title: title, img: img, video: video, description: description, genre: genre, maturity: maturity, time: time, year: year, acting: acting, directors: directors, writers: writers, producers: producers, watched: watched, rating: rating))
}
}
}
}
Got it working! Just had to use updateData method.
let x = db.collection("movies").document(SectionX.id).documentID
db.collection("movies").document(x).updateData(["time":currentTime])
Good day, I'm trying to pass data to my Profile UI View.
This is my Customer Class:
class Customer {
// Creating a customer
let name: String
let surname: String
let contactNo: String
let email: String
init(name: String,surname: String,contactNo: String,email: String) {
self.name = name
self.surname = surname
self.contactNo = contactNo
self.email = email
}
}
This is my code whereby I try to parse data from Firestore to display a customers details:
class ProfileCus: UIViewController {
// Labels to display data
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var surnameLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
#IBOutlet weak var contactLabel: UILabel!
// Reference to customer collection in Firestore
private var customerRefCollection: CollectionReference!
// Customer Object
private var customer: Customer?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
customerRefCollection = Firestore.firestore().collection("customers")
nameLabel.text = customer?.name
surnameLabel.text = customer?.surname
emailLabel.text = customer?.email
contactLabel.text = customer?.contactNo
}
// This function notifies the view controller that the view is about to load so it is best to acquire the data in here before the view loads so that it will be ready to display.
override func viewWillAppear(_ animated: Bool) {
// Get the current user ID
let userID = Auth.auth().currentUser?.uid
// Locate the user information on Firestore
customerRefCollection.document(userID!).getDocument { (snapshot, error) in
if let err = error {
debugPrint("Error fetching documents: \(err)")
}
else {
// Ensure that if there's nothing in the document that the function returns
guard let snap = snapshot else {return}
// Parse the data to the customer model
let data = snap.data()
let name = data?["name"] as? String ?? ""
let surname = data?["surname"] as? String ?? ""
let email = data?["email"] as? String ?? ""
let contact = data?["contact no"] as? String ?? ""
// Create the customer and pass it to the global variable
let cus = Customer(name: name, surname: surname, contactNo: contact, email: email)
self.customer = cus
}
}
}
}
Everything on the Firestore side is working fine and I am able to read/retrieve data but I'm struggling to pass the data to my UI via my Customer Class. Using print statements it seems as if the customer object is nil.
Output once the code runs
func getDataFromFirebase(completion:#escaping() -> ()){
let userID = Auth.auth().currentUser?.uid
// Locate the user information on Firestore
customerRefCollection.document(userID!).getDocument { (snapshot, error) in
if let err = error {
debugPrint("Error fetching documents: \(err)")
}
else {
// Ensure that if there's nothing in the document that the function returns
guard let snap = snapshot else {return}
// Parse the data to the customer model
let data = snap.data()
let name = data?["name"] as? String ?? ""
let surname = data?["surname"] as? String ?? ""
let email = data?["email"] as? String ?? ""
let contact = data?["contact no"] as? String ?? ""
// Create the customer and pass it to the global variable
let cus = Customer(name: name, surname: surname, contactNo: contact, email: email)
self.customer = cus
}
completion()
}
}
getDataFromFirebase{
customerRefCollection = Firestore.firestore().collection("customers")
nameLabel.text = customer?.name
surnameLabel.text = customer?.surname
emailLabel.text = customer?.email
contactLabel.text = customer?.contactNo
}
So what you're basically doing here is you're first getting the data from the firebase and only after this work is done, hence the completion block you will be setting your data to the view. You can call the method simply in viewDidLoad.
this one for sending message and save it to realm db
var messageIndex = try! Realm().objects(MessageRealm.self).sorted(byKeyPath: "timeStamp")
func didPressSend(text: String) {
if self.inputContinerView.inputTextField.text! != "" {
let messageDB = MessageRealm()
let realm = try! Realm()
let userRealm = UsersRealm()
messageDB.textDownloadded = text
messageDB.fromId = user!.fromId
messageDB.timeStamp = Date()
print(messageDB)
try! realm.write ({
print(realm.configuration.fileURL)
userRealm.msgs.append(messageDB)
//realm.create(MessageRealm.self, value: ["textDownloadded": text, "fromId": user!.fromId, "timeStamp": Date()])
})
if let userTitleName = user?.toId {
print(userTitleName)
OneMessage.sendMessage(text, thread: "AAAWatree", to: userTitleName, isPhoto: false, isVideo: false, isVoice: false, isLocation: false, timeStamp: date, completionHandler: { (stream, message) in
DispatchQueue.main.async {
OneMessage.sharedInstance.deleteCoreDataMessage()
}
self.inputContinerView.inputTextField.text! = ""
})
}
}
}
This for when recieving message im trying to save user (send id )
let realm = try! Realm()
userData.sender = sender
userData.toId = toUser
print(userData.sender)
print(userData.toId)
try! realm.write ({
realm.add(userData, update: true)
})
this my Realm Object Class
class MessageRealm: Object {
dynamic var textDownloadded = String()
dynamic var imageDownloadded = NSData()
dynamic var videoDownloadded = String()
dynamic var voiceDownloadded = String()
dynamic var fromId = String()
dynamic var timeStamp = Date()
dynamic var messageId = NSUUID().uuidString
let userSelect = List<UsersRealm>()
override class func primaryKey() -> String? {
return "messageId"
}
}
class UsersRealm: Object {
dynamic var sender = String()
dynamic var fromId = String()
dynamic var toId = String()
dynamic var lastMessage = String()
dynamic var timeStamp = Date()
dynamic var profileImage = NSData()
let msgs = List<MessageRealm>()
override class func primaryKey() -> String {
return "sender"
}
}
sending and reciving is ok and its save to realm db but all any user send message i recived in one user i want to seprate for every user have his sending and recive database i miss something here but i dont know i try to search nothing its long question but i cant figure out the soluation
and sorry for my week english
Thank you
If I understood your case correctly you're using a single realm url for all users that's why all your clients have the same data. You should probably create a separate realm for the conversation and share it between the users who participate in that chat. Please learn more about sharing realms in our docs at https://realm.io/docs/swift/latest/#access-control.