How can I display my user custom data in String format? - swift

I'm new to Swift UI and MongoDB Realms. I'm trying to display a user's email (from their respective custom user data) in a text box but I'm getting a weird result that I don't know how to fix. I've been semi-blindly trying all sorts of things hoping something would work but no luck so far. I'm using Email/Password authorization so I know that the email/password wouldn't be stored in the user custom data (I think) but its just an example for what I'm trying to do.
My current code so far below.
struct HomeView: View {
let user = app.currentUser!
#State var isLoggingOut = false
var body: some View {
let userEmail = user.customData["email"] ?? "no email"
let userPassword = user.customData["password"] ?? "no password"
let userDisplay = user.customData["display"] ?? "no display"
let userName = user.customData["fullName"] ?? "no name"
ZStack {
Rectangle().foregroundColor(.yellow)
VStack {
Spacer()
Text("Home")
HStack {
Text("Email").frame(width: 100)
Spacer()
Text(String(reflecting: userEmail))
}.padding(.vertical)
HStack {
Text("Password").frame(width: 100)
Spacer()
Text(String(describing: userPassword))
}.padding(.vertical)
HStack {
Text("Display").frame(width: 100)
Spacer()
Text(String(describing: userDisplay))
}.padding(.vertical)
HStack {
Text("Full name").frame(width: 100)
Spacer()
Text(String(describing: userName))
}.padding(.vertical)
Spacer()
Button("Log Out") {tryLogOut()}
}.padding(40)
if isLoggingOut {LoginView()}
}
}
func tryLogOut() {
app.currentUser?.logOut {error in}
self.isLoggingOut = true
}
}
After logging in with a test user, this is what I'm getting in the right HStack text boxes (for example, the top email text box):
Email Optional(RealmSwift.AnyBSON.string("test123#gmail.com"))
Obviously what I'm trying to end up with is:
Email test123#gmail.com
What am I doing wrong? Everything else works as intended but this problem is giving me a headache. Any help would be appreciated.
Also FYI - Everything I am trying to display in the text boxes is stored in the database as Strings according to Atlas so I don't see the problem. However in my NewUserRegistrationView, when I create the new user document, I use the following code, I'm not sure if there is anything conflicting with the AnyBSON types before inserting the document.
struct NewUserRegistrationView: View {
// Email, password, displayName, and fullName obtained from TextFields in the body ...
// createUserDocument() is called after registering and confirming the user
func createUserDocument() {
let credentials = Credentials.emailPassword(
email: self.email,
password: self.password)
app.login(credentials: credentials) {result in
switch result {
case .failure:
self.statustext = "Document creation failed, try again"
case .success(let user):
let client = user.mongoClient("mongodb-atlas")
let database = client.database(named: "AppDatabase")
let collection = database.collection(withName: "userDocuments")
collection.insertOne([
"userID": AnyBSON(user.id),
"email": AnyBSON(self.email),
"password": AnyBSON(self.password),
"display": AnyBSON(self.display),
"fullName": AnyBSON(self.fullName)
]) { result in
switch result {
case .failure:
self.statustext = "Could not add document"
case .success(let newObjectId):
self.statustext = "Inserted document with objID: \(newObjectId)"
self.isDone = true
}
}
}
}
}

First of all conditionally downcast all dictionary values to AnyBSON to get the String value
let userEmail = (user.customData["email"] as? AnyBSON)?.stringValue ?? "no email"
let userPassword = (user.customData["password"] as? AnyBSON)?.stringValue ?? "no password"
let userDisplay = (user.customData["display"] as? AnyBSON)?.stringValue ?? "no display"
let userName = (user.customData["fullName"] as? AnyBSON)?.stringValue ?? "no name"
Without the cast you get Any which prints the weird output using String(reflecting
Then simply write
Text(userEmail)
...
Text(userPassword)
...
Text(userDisplay)
...
Text(userName)

It is because you are using String(...) change it to 'userEmail.description ?? ""`

The best way to display user data is to have a function in your class/struct that converts user data in whatever form to a string and then displays it. This would allow you to just use one function to convert data

I managed to figure it out but I have absolutely no idea why it works or what is happening. Lucky enough, I got it by brute force trial and error. Other answers didn't work unfortunately, but thanks to those for the suggestions.
I changed:
let userEmail = user.customData["email"] ?? "no email"
let userPassword = user.customData["password"] ?? "no password"
let userDisplay = user.customData["display"] ?? "no display"
let userName = user.customData["fullName"] ?? "no name"
To this:
let userEmail = ((user.customData["email"] ?? "email")?.stringValue)!
let userPassword = ((user.customData["password"] ?? "password")?.stringValue)!
let userDisplay = ((user.customData["display"] ?? "display")?.stringValue)!
let userName = ((user.customData["fullName"] ?? "fullName")?.stringValue)!
Text(userEmail)
Text(userPassword)
Text(userDisplay)
Text(userName)
Can anyone breakdown how this works? Like what do the question/exclamation marks do? And what is a simpler way to do this (if any)?

This solves the problem of decoding any AnyBSON. Not, user.customData...which in fact always returns nil!
I'm upset that it took so long to basically guess at the solution. And even this solution needs a refactor as I'm force unwrapping which means its a ticking bomb.
So I used the Realm call gist:
collection.findOneDocument(filter: ["userId": AnyBSON(user.id)])
Using the result:
case .success(let document):
let email = document?["email"] ?? "no email"
print("Email: \(String(describing: email!.stringValue!))")
So this returns the email string by itself. The fact I had to double-unwrap is insane.
Naturally, you want to guard and or if let as necessary. The document in this case is a Realm Document which is: Dictionary <String : AnyBSON?>

Related

SwiftUI - only one alert showing in nested if

So I am creating a register view and I have two alerts to show. One is showing if the email or the password don't meet the requirements and the other one is used if the email is already saved in the system. The issue is that the single alert showing up is the one that checks if the email and password meet that set of requirements.
Button ("Sign Up") {
toggle.toggle()
if (isValidEmailAddr(strToValidate: userCredentials.email) && isValidPassword(strToVailidate: userCredentials.password)) {
if !emailDoesExist(emailToCheck: userCredentials.email) {
print("Email and Passowrd are valid")
saveJSON(email: userCredentials.email, password: userCredentials.password, nickname: userCredentials.nickname)
} else {
print("email address already in the system")
self.showAlert1.toggle()
}
}
else {
self.showAlert0.toggle()
}
}
.alert(isPresented: $showAlert1, content: { self.alert1 })
.alert(isPresented: $showAlert0, content: { self.alert0 })
.font(.title)
.buttonStyle(.bordered)
.frame(width:200, height: 100)
The problem is that you cannot attach more than one alert to the same view and now you have two for your button.
There are several ways to approach this to get only one alert and here I use a simple one where I use a second property that holds an error message that can be used in the alert
#State private var showAlert = false
#State private var errorMessage = ""
if (isValidEmailAddr(strToValidate: userCredentials.email) && isValidPassword(strToVailidate: userCredentials.password)) {
if !emailDoesExist(emailToCheck: userCredentials.email) {
saveJSON(email: userCredentials.email, password: userCredentials.password, nickname: userCredentials.nickname)
} else {
errorMessage = "Mail already exists"
self.showAlert.toggle()
}
} else {
errorMessage = "Mail or password is incorrect"
self.showAlert.toggle()
}
Then you would need to adjust the actual .alert modifier but since I don't know what self.alert0/1 are you need to do that yourself (most likely by keeping only one of them and make use of errorMessage).

Straightforward way to add timestamp to Firebase in Swift?

so I've been researching a lot and apparently no method is working to add timestamp to my firebase data and then sort the data accordingly. I tried the traditional "timestamp": [".sv":"timestamp"] method and that only adds a value of .sv: Timestamp in firebase. Then I tried self.createdAt = Date(timeIntervalSince1970: timestamp/1000) and added that to where I add the item to firebase but that didn't help either.So doesn't anyone know what's the most straightforward method to add timestamp to firebase via swift? Would appreciate any input! Thanks.
EDIT:
Here's now I define the item being added to firebase:
struct Note: Identifiable, Codable {
var id: String
var content: String
var createdAt: String
}
Here's how I define fetching that item from firebase:
func fetchNotes () {
notes.removeAll()
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
let ref = db.collection("userslist").document(uid).collection("actualnotes")
ref.order(by: "createdAt", descending: true).addSnapshotListener{ (querySnapshot, error) in
let documents = querySnapshot!.documents
if querySnapshot!.isEmpty {
let ref = db.collection("userslist").document(uid).collection("actualnotes")
ref.document().setData(["content": "Welcome", "createdAt": FieldValue.serverTimestamp() ])
}
else {
self.notes = documents.map { (queryDocumentSnapshot) -> Note in
let data = queryDocumentSnapshot.data()
let id = queryDocumentSnapshot.documentID
let content = data["content"] as! String ?? ""
let createdAt = data["createdAt"] as? String ?? "" // this is where
// it breaks with the error (Thread 1: signal SIGABRT)
let note = Note(id:id, content:content, createdAt: createdAt)
return (note)
}
}
}
}
And here's where I want the timestamp to appear in another view:
List{ ForEach(dataManager.notes) { note in
NavigationLink(destination: NoteView(newNote: note.content, idd: note.id ))
{
HStack {
Text(note.createdAt)
}})}}
The [".sv":"timestamp"] applies to the Realtime Database only, while you are using Cloud Firestore. While both databases are part of Firebase, they are completely separate and the API of one does not apply to the other.
For Firestore follow its documentation on writing a server-side timestamp, which says it should be:
db.collection("objects").document("some-id").updateData([
"lastUpdated": FieldValue.serverTimestamp(),
])
If that doesn't work, edit your question to show the exact error you get.

Document ID retrieval

this is supposed to take the user ID from the result!.user.uid and store it a variable or function in order for me to use it again.
the problem is that I dont know how to get it to store the value outside of this function.
Ive tried to make it store to a variable outside of the initial button function, and Ive also tried to return it outside of the function by removing a part of the code which made it become a void. Im not sure where i need to go/what else I can try and do in order to fix this problem.
If anybody know how do I retrieve my document ID from this code your help would be greaty appreciated
#IBAction func NextButtonTapped(_ sender: Any) {
//validate the fileds
let Error = validateFields()
if Error != nil {
// there is somthing wrong with the fields show error message
showError(Error!)
}
else {
// create cleaned versions of the data
let Password = PasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Email = EmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Firstname = FirstnameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Lastname = LastnameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Age = AgeTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// create the user
Auth.auth().createUser(withEmail: Email, password: Password) { (results, Err) in
// check for errors
if Err != nil {
// there was an error creating the user
self.showError("Error creating user")
}
else {
// user was created succesfully store first and last name
let db = Firestore.firestore()
db.collection("users").document(results!.user.uid).setData(["first name":Firstname, "last name":Lastname, "age":Age, "uid":results!.user.uid]) { (Error) in
if Error != nil {
// show error message
self.showError("error saving user data")
}
//showing users document id
}
//transition to the home screen
self.transitionToHome()
}
}
}
}
I have no idea what to do any help would be amazing,
thank you very much!!!!
Define a uid variable outside of the IBAction function like so.
var uid: String? = nil
Then, within the createUser function
self.uid = results!.user.uid

OpenWeatherMap API does not find all cities

I was following this tutorial (https://www.youtube.com/watch?v=QMR6N6Vs_x0&feature=emb_err_woyt) and used the code for an Apple Watch Application. So everything works almost fine.
The strange thing is that not all the cities that I'm typing are accepted. It gives the error (see code) "incorrect user input!". But when I copy and paste the url that is generated and not correct due the code, it will work in de browser. It will accept that input. But my app won't.
I have searched but could not find anything similar. I think it is a very small thing to fix.
import Foundation
final class NetService: ObservableObject {
#Published var weather: WeatherData?
#Published var city = ""
let baseURL = URL(string: "https://api.openweathermap.org/data/2.5/weather")!
var query = ["q": "", "appid": "", "units": "metric"]
func loadWeatherInfo(by city: String) {
guard baseURL.withQueries(query) != nil, city != "" else { print("URL isn't correct!"); return}
query["appid"] = "HERE I PUT MY OWN API-KEY"
query["q"] = city
URLSession.shared.dataTask(with: baseURL.withQueries(query)!) { data, _, error in
print(self.baseURL.withQueries(self.query)!)
guard let data = data else { print(#line, #function, "\(error!.localizedDescription)"); return }
if let weatherInfo = try? JSONDecoder().decode(WeatherData.self, from: data) {
DispatchQueue.main.async { [weak self] in
self?.weather = weatherInfo
}
} else {
print(#line, #function, "incorrect user input!"); return
}
}.resume()
}
}
So my question is, why won't my code accept all the cities. I get the error on "Zeist", "Woerden", "Gent" and many more. If I copy the "build url" (https://api.openweathermap.org/data/2.5/weather?q=Zeist&appid=HERE I PUT MY OWN API-KEY&units=metric) from the console I get a return when i paste this in Safari. So the city is recognised.
{"coord":{"lon":5.23,"lat":52.09},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"base":"stations","main":{"temp":11.84,"feels_like":10.18,"temp_min":11.11,"temp_max":12.78,"pressure":1020,"humidity":58},"wind":{"speed":0.45,"deg":264,"gust":2.68},"clouds":{"all":7},"dt":1589534449,"sys":{"type":3,"id":2005313,"country":"NL","sunrise":1589514260,"sunset":1589570804},"timezone":7200,"id":2743977,"name":"Zeist","cod":200}
So the url is build correctly.
Thank for reading, I hope there is a solution.
Thanks to the comments above this post I have found the solution. In my WeatherData visibility is something that I collect from OpenWeatherMap. Not all cities have "visibility" as an output, so I have made this an optional and it works now!

if-else statements executing twice inside the closure in textFieldShouldReturn function

I have a UItextField called addUserField in which a user can enter an email after the user hits enter I am checking if that email exists in my Firebase DB and if it does it must print "OK"\email and if does not then it should print "BAD"\email. However my code below is not doing what its intended to, it is somehow executing the if conditions twice and I am not able to figure out why.
So incase the email exists in the database it is printing:
OK xyz#abc.com
BAD xyz#abc.com
Incase the email does not exist in the database it is printing:
BAD abc#xyz.com
BAD abc#xyz.com
Can anybody help me figure out why the if statement is executing twice and how can I make it so that it just print OK if email exists or BAD if it does not.
func textFieldShouldReturn(_textField: UITextField!) -> Bool {
var email: String = ""
if let userEmail = addUserField.text{
email = userEmail
}
let userRef = ref.child("users");
userRef.queryOrderedByChild("role").queryEqualToValue("User").observeEventType(.Value, withBlock: {
snapshot in
for child in snapshot.children {
let username = child.value["email"] as! String
if (email == username){
print ("OK \(email)")
}
else {
print ("BAD \(email)")
}
}
})
return true
}