How can I display name of User object in ProfileViewController - ios

I need to display user.username on Text in ProfileView but I got error when I try to fill current user with User. I have to get User in currentUser var.
import Foundation
import FirebaseAuth
import Firebase
class AuthViewModel: ObservableObject
{
#Published var userSession: FirebaseAuth.User?
#Published var currentUser: User?
private var tempUserSession: FirebaseAuth.User?
private let service = UserService()
init()
{
self.userSession = Auth.auth().currentUser
}
func login(withEmail email: String, password: String)
{
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
if let e = error
{
print(e.localizedDescription)
}
else
{
guard let user = authResult?.user else {return}
self.userSession = user
guard let uid = self.userSession?.uid else { return }
self.service.fetchUser(withUid: uid)
print("Did User log IN")
}
}
}
func fetchUser()
{
guard let uid = self.userSession?.uid else { return }
service.fetchUser(withUid: uid) { user in <----- HERE I GOT AN ERROR Extra trailing closure passed in call
self.currentUser = user
}
}
}
import Foundation
import UIKit
import FirebaseAuth
import SwiftUI
class ProfileViewController: UIViewController
{
var authViewModel = AuthViewModel()
#IBOutlet weak var userNameLabel: UILabel!
override func viewDidLoad()
{
navigationItem.hidesBackButton = true
userNameLabel.text = authViewModel.currentUser?.username // HERE I WANT TO DISPLAY CURRENT USER - USERNAME
print(userNameLabel.text)
}
}
I tried fill currentuser with user but nothings worked. Still in profile view controller I got nill. (currentUser.username = nil)

The code calls fetchUser like it's an asynchronous function and it's not; it's a synchronous function that does not return a value, nor has an escaping completion handler. So that's the cause of the error.
Here's how I would do it. Start with a a simple user class
class MyUser {
var userName = ""
var uid = ""
}
and then a simplified fetchUser using async/await
func fetchUser() {
Task {
let uid = "uid_0"
let foundUser = await self.getUserAsync(withUid: "uid_0")
print(foundUser.userName)
}
}
and then the code to fetch the user from Firestore, instantiate a MyUser object and return it
func getUserAsync(withUid: String) async -> MyUser {
let usersCollection = self.db.collection("users") //self.db points to my firestore
let thisUserDoc = usersCollection.document(withUid)
let snapshot = try! await thisUserDoc.getDocument()
let user = MyUser()
user.userName = snapshot.get("userName") as? String ?? "No Name"
user.uid = withUid
return user
}

Related

How do I take the name of the Firebase document? Swift

How do I take the name of the Firebase document, directly from the information of the logged in user?
Basically I have a user collection and a Degree Course collection.
When I use the func
GetCorsodiLaurea
I don't want to manually insert the document name in .document ("")
But I would like to automatically take the name of the document directly from the user's info
The field that declares which course is connected to the user is "TipoCorso".
As you can see in the Degree Courses collection there is the value of the "TipoCorso" field
Here is the code of the function and a screen of the Firebase Database:
import SwiftUI
import Firebase
import FirebaseFirestoreSwift
import FirebaseDatabase
class ProfileViewModel : ObservableObject{
#Published var userInfo = UserModel(Nome: "", Cognome: "", photoURL: "", Nomeintero: "", Corsodilaurea: "", Tipocorso: "")
#Published var userDegree = userDegreeModel(Name: "", TotalSubjects: "")
var ref = Firestore.firestore()
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
fetchUser()
GetCorsodiLaurea()
}
func fetchUser() {
ref.collection("users").document(uid).getDocument { [self] (doc, err) in guard let user = doc else { return }
let Nome = user.data()?["Nome"] as! String
let Cognome = user.data()?["Cognome"] as! String
let photoURL = user.data()?["photoURL"] as! String
let Nomeintero = user.data()?["Nomeintero"] as! String
let Tipocorso = user.data()?["Tipocorso"] as! String
let Corsodilaurea = user.data()?["Corsodilaurea"] as! String
DispatchQueue.main.async {
self.userInfo = UserModel(Nome: Nome, Cognome: Cognome, photoURL: photoURL, Nomeintero: Nomeintero, Corsodilaurea: Corsodilaurea, Tipocorso: Tipocorso) }
}
}
func GetCorsodiLaurea() {
db.collection("DegreeCourses").document(self.userInfo.Tipocorso).getDocument { [self] (doc, err) in guard let DegreeCourses = doc else { return }
let Name = DegreeCourses.data()?["Name"] as! String
let TotalSubjects = DegreeCourses.data()?["TotalSubjects"] as! String
// [END doc_reference]
// [END collection_reference]
DispatchQueue.main.async {
self.userDegree = userDegreeModel(Name: Name, TotalSubjects: TotalSubjects)
}
}
}
}
User
DegreeCourses
When you call the fetchUeser() function it looks like you are populating the UserModel with the specific user's Tipocorso.
So in the GetCorsodiLaurea function you can call Tipocorso member in userInfo variable.
ref.collection("DegreeCourses").document(self.userInfo.Tipocorso).getDocument { [self] (doc, err) in guard let DegreeCourses = doc else { return }
Edit: You are most likely getting the error because the fetchUsers() function hasn't completed fully (as it is waiting for Firebase to respond) but the execution has already proceeded to the GetCorsodiLaurea() function.
To fix this add, a escaping closure to the fetchUsers() function and call the GetCorsodiLaurea() function in the closure. This way, the compiler won't try and execute the functions asynchronously.
import SwiftUI
import Firebase
import FirebaseFirestoreSwift
import FirebaseDatabase
class ProfileViewModel : ObservableObject{
#Published var userInfo = UserModel(Nome: "", Cognome: "", photoURL: "", Nomeintero: "", Corsodilaurea: "", Tipocorso: "")
#Published var userDegree = userDegreeModel(Name: "", TotalSubjects: "")
var ref = Firestore.firestore()
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
//GetCorsodilaurea() will only get called after fetchUser() is complete
fetchUser(completion: {
GetCorsodiLaurea()
})
}
func fetchUser(completion: #escaping () -> Void)) {
ref.collection("users").document(uid).getDocument { [self] (doc, err) in guard let user = doc else { return }
let Nome = user.data()?["Nome"] as! String
let Cognome = user.data()?["Cognome"] as! String
let photoURL = user.data()?["photoURL"] as! String
let Nomeintero = user.data()?["Nomeintero"] as! String
let Tipocorso = user.data()?["Tipocorso"] as! String
let Corsodilaurea = user.data()?["Corsodilaurea"] as! String
//don't do this async
self.userInfo = UserModel(Nome: Nome, Cognome: Cognome, photoURL: photoURL, Nomeintero: Nomeintero, Corsodilaurea: Corsodilaurea, Tipocorso: Tipocorso)
completion()
}
}
func GetCorsodiLaurea() {
db.collection("DegreeCourses").document(self.userInfo.Tipocorso).getDocument { [self] (doc, err) in guard let DegreeCourses = doc else { return }
let Name = DegreeCourses.data()?["Name"] as! String
let TotalSubjects = DegreeCourses.data()?["TotalSubjects"] as! String
// [END doc_reference]
// [END collection_reference]
DispatchQueue.main.async {
self.userDegree = userDegreeModel(Name: Name, TotalSubjects: TotalSubjects)
}
}
}

How to conditionally load view upon app launch in SwiftUI?

I'm currently trying to implement an auto-login feature to my app using UserDefaults. What I would like to do before loading any view is get the UserDefaults email and password and call the login function from my API. If successful, go to Home view, else go to LoginView. My apologies, I'm very new to Swift and on a tight schedule with my project. Here is my code segment. I'm not sure where I can add my logic:
import SwiftUI
#main
struct MyApp: App {
init() {
let email = UserDefaults.standard.string(forKey: "email");
let pw = UserDefaults.standard.string(forKey: "pw");
let api = MyAppAPI()
api.signInUser(email: email, password: pw) { result in
//JSON response contains an 'isError' field
let isError = result.value(forKey: "error") as! Bool
if !isError {
//successful login - what to do from here?
}
}
}
var body: some Scene {
WindowGroup {
LoginView()
}
}
}
Here is a simple way of doing this, you can do this onAppear
import SwiftUI
struct ContentView: View {
let email: String
let pass: String
init() {
self.email = UserDefaults.standard.string(forKey: "email") ?? ""
self.pass = UserDefaults.standard.string(forKey: "pw") ?? ""
}
#State private var result: Bool?
var body: some View {
Group {
if let unwrappedResult: Bool = result {
if unwrappedResult {
Text("Home View, Welcome!")
}
else {
Text("Wrong User or Pass, try again!")
}
}
else {
Text("loading...")
}
}
.onAppear() { loginFunction(email: email, pass: pass) { value in result = value } }
}
}
func loginFunction(email: String, pass: String, completion: #escaping (Bool) -> Void) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(3000)) { completion(Bool.random()) }
}

Returning Nil When Running Method from separate class [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 2 years ago.
I have a View Controller that attempts to call a method from my UserModel class which gets a user document and fits the return data into a User structure. However, it is telling me it unexpectedly finds nil when unwrapping an optional value.
My UserModel:
class UserModel {
var user:User?
func getUser(userId: String) -> User? {
let docRef = Firestore.firestore().collection("Users").document(userId)
// Get data
docRef.getDocument { (document, error) in
if let document = document, document.exists {
var user:User = User(name: document["name"] as! String, phone: document["phone"] as! String, imageUrl: document["imageUrl"] as! String)
} else {
print("Document does not exist")
}
}
return user!
}
}
My Structure:
struct User {
var name:String
var phone:String
var imageUrl:String
}
My ViewController:
override func viewDidLoad() {
super.viewDidLoad()
userId = Auth.auth().currentUser?.uid
}
override func viewDidAppear(_ animated: Bool) {
let model = UserModel()
user = model.getUser(userId: userId!)
print(user?.name)
}
The method runs fine when it is inside my View Controller, so I know it's getting the uid, the database call works, and the values all exist. I have printed them all separately. However, within its own class it doesn't work.
Any ideas?
It looks like getDocument is an async function. Hence, you should make getUser async:
func getUser(userId: String, completion: #escaping (User?) -> Void) {
let docRef = Firestore.firestore().collection("Users").document(userId)
// Get data
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let user:User = User(name: document["name"] as! String, phone: document["phone"] as! String, imageUrl: document["imageUrl"] as! String)
completion(user)
} else {
completion(nil)
}
}
}
This is how you should call it:
let model = UserModel()
model.getUser(userId: userId!) { user in
print(user?.name)
}

How can I connect a post to user and display that user and post data in a Custom cell in TableView using Firebase

I am building a social media app and since I wasn't able to solve this by myself, I need help.
I have connected my Xcode project to the Firebase and made it possible for my users to register/sign in and publish Posts to the Firebase which are then shown all together in one group TableView but none of the data is connected to the user which posted that Post. The idea is that it looks similar to Instagram posts, but every post in my app would have to include only: Photo and Caption(optional) which are part of a Post Class, and CraftName which is a part of User Class. I believe that the problem lies in "denormalization" and incorrect populating of tableView.
Here is a photo of my Firebase tree which currently has 2 users signed in. One has posted 1 Post, and another has posted 2 Posts
https://i.imgur.com/fc2LEMk.jpg
I successfully register Users( with email, username and CraftName) to firebase database and I have made it possible for them to sign in and sign out so I believe the problem is somewhere else to look. I have also made it possible to post a Post to Firebase which includes Photo and Caption and to populate the tableView with that two objects. Only thing left is to connect user with it's Post and while displaying the Post, provide that User's CraftName as a TextView above the Post.
This is AuthService class which deals with registration
static func register(username: String, email: String, password: String, craftName: String, onSuccess: #escaping () -> Void, onError: #escaping (_ errorMessage: String?) -> Void) {
Auth.auth().createUser(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
onError(error!.localizedDescription)
return
}
let uid = Auth.auth().currentUser!.uid
self.setUserInformation(username: username, email: email, craftName: craftName, uid: uid, onSuccess: onSuccess)
})
}
This is a registration class
#IBAction func registerButtonPressed(_ sender: Any) {
view.endEditing(true)
AuthService.register(username: usernameTextField.text!, email: emailTextField.text!, password: passwordTextField.text!, craftName: craftNameTextField.text!, onSuccess: {
self.performSegue(withIdentifier: "registerToTabBar", sender: self)
}, onError: {error in
ProgressHUD.showError(error!)
})
}
}
//Stvaranje reference na Firebase Realtime bazu podataka i spremanje svih podataka svakog korisnika zasebno u tu bazu podataka
static func setUserInformation(username: String, email: String, craftName: String, uid: String, onSuccess: #escaping () -> Void){
let ref = Database.database().reference()
let usersReference = ref.child("users")
let newUserReference = usersReference.child(uid)
newUserReference.setValue(["username": username, "email": email, "craftName": craftName])
onSuccess()
}
}
This is my User Class
struct User {
var username: String?
var email: String?
var craftName: String
init(craftNameString: String, emailString : String, usernameString: String){
craftName = craftNameString
username = usernameString
email = emailString
}
}
This is my Post Class
class Post {
var caption: String?
var photoUrl: String?
var numberOfClaps: Int?
init(captionText: String, photoUrlString: String) {
caption = captionText
photoUrl = photoUrlString
}
}
This is my Post custom cell class
class PostCell: UITableViewCell {
#IBOutlet weak var postImageView: UIImageView!
#IBOutlet weak var captionTextViev: UITextView!
#IBOutlet weak var craftNameTextField: UITextView!
var post : Post! {
didSet{
self.updatePostUI()
}
}
var user : User! {
didSet{
self.updateUserUI()
}
}
func updatePostUI() {
captionTextViev.text = post.caption
}
func updateUserUI(){
craftNameTextField.text = user.craftName
}
}
Now when my users are Registered, they can post a Post in CameraClass.
#IBAction func shareButtonPressed(_ sender: Any) {
if let postImg = selectedImage, let imageData = postImg.jpegData(compressionQuality: 1) {
let photoIDString = NSUUID().uuidString
print(photoIDString)
let storageRef = Storage.storage().reference(forURL: Config.STORAGE_ROOT_REF).child("posts").child(photoIDString)
storageRef.putData(imageData, metadata: nil, completion: { (metadata, error) in
if error != nil {
ProgressHUD.showError(error?.localizedDescription)
return
}
storageRef.downloadURL(completion: { (url, error) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
}else {
if let photoURL = url?.absoluteString{
self.sendDataToDatabase(photoUrl: photoURL)
}
}
})
})
}
}
func sendDataToDatabase(photoUrl: String) {
let ref = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
let postsReference = ref.child("posts")
let userUID = postsReference.child(uid)
let newPostID = userUID.child(userUID.childByAutoId().key!)
newPostID.setValue(["photoUrl": photoUrl, "caption": captionTextView.text!], withCompletionBlock: { (error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
ProgressHUD.show("UspjeĆĄno ste objavili fotografiju")
ProgressHUD.dismiss()
self.clean()
self.tabBarController?.selectedIndex = 0
})
This is my HomeViewController in which is the tableView that is supposed to display Posts
class HomeViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var posts = [Post]()
struct Storyboard {
static let postCellDefaultHeight : CGFloat = 578.0
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.estimatedRowHeight = Storyboard.postCellDefaultHeight
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorColor = UIColor.clear
loadPosts()
}
//Function which is supposed to retrieve data from database and populate the TableView
func loadPosts() {
let ref = Database.database().reference()
let posts = ref.child("posts")
posts.observe(.value) { (snapshot) in
for currentUser in (snapshot.children) {
let cUSer = currentUser as! DataSnapshot
for postInfo in (cUSer.children) {
let postSnap = postInfo as! DataSnapshot
let dict = postSnap.value as? [String: Any]
let captionText = dict!["caption"] as! String
let photoUrlString = dict!["photoUrl"] as! String
let post = Post(captionText: captionText, photoUrlString: photoUrlString)
self.posts.append(post)
self.tableView.reloadData()
}
}
}
}
It looks like this right now:
https://i.imgur.com/lMxZYLW.jpg
This is how I would like it to look:
https://i.imgur.com/721mEXm.jpg

How do I get Firebase database value into UILabel text?

I'm trying to read a value from my Firebase database. I then want to change UILabel text to the database child value. Seems pretty simple, but I cannot figure out why the value is reading blank.
Here is my Firebase JSON:
{
"pilots" : {
"HpPzn0XUqMgsKhUOpH75lHIhyFA3" : {
"pilot" : "First Lastname",
"weight" : 180
}
}
}
Here are the Firebase rules, just for testing at the moment:
{
"rules": {
"pilots": {
".read": true,
".write": true
}
}
}
Finally the Swift 3 code, which is probably ugly as sin. First app after reading and online lessons.
import UIKit
import Firebase
import FirebaseCore
import FirebaseAuth
import FirebaseDatabase
class MainMenuViewController: UIViewController {
#IBOutlet weak var pilotUsername: UILabel!
#IBOutlet weak var dateTime: UILabel!
#IBOutlet weak var aircraftLabel: UILabel!
#IBOutlet weak var riskScoreInt: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let ref = FIRDatabase.database().reference()
let userID = FIRAuth.auth()?.currentUser?.uid
ref.child("pilots").child(userID!).child("pilot").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.pilotUsername.text = username
print(username)
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
I'm just using the example code from the Firebase documentation. There's a line of code (in the example) after
let username = value?["username"] as? String ?? ""
that is :
let user = User.init(username: username)
but it gives me an error. "Use of unresolved identifier 'User'"
I don't think I need that line of code, since nothing like it is used in the examples and lessons that I've folowed.
Thank's in advance. This is my first time posting to Stack Overflow.
import FirebaseDatabase
class User {
// MARK: Properties
var firstname: String
var lastname: String
var username: String { return "\(firstname)\(lastname)" }
// MARK: Initializers
init(firstname: String, lastname: String) {
self.firstname = firstname
self.lastname = lastname
}
init?(snapshot: FIRDataSnapshot) {
guard
let firstname = snapshot.childSnapshot(forPath: "First").value as? String,
let lastname = snapshot.childSnapshot(forPath: "Lastname").value as? String
else { return nil }
self.firstname = firstname
self.lastname = lastname
}
}
Then
ref.child("pilots").child(userID!).child("pilot").observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
if let user = User(snapshot: snapshot) {
self?.pilotUsername.text = user.username
}
}) { (error) in
print(error.localizedDescription)
}

Resources