Issue with Firebase Storage & Database - swift

I'm having an app, with UpdateProfileViewController (iOS app, written in Swift). I'm using Firebase storage and Database as backend. The problem that I'm facing is that I have 2 images that need to be uploaded to the Firebase storage, then get the download URL and enter it in the database for that particular user. Now, I'm facing 3 problems.
The downloadURL method should return a URL pointing to the uploaded image in the storage and assign it to the variable storageHeaderDownloadedURL and the other one is headerImgDownloadedURL (those should be the header and the profile images).
I want to update all of the fields despite of the fact if the user changed something or not, just update the entire user profile.
I would like to be able to compress the the images and make them smaller in terms of size, not quality, so the users don't upload very big images.
Here's my entire code, I hope someone could help me out with that because I spent 2 days and I still cannot figure those things out:
#IBAction func savePressed(_ sender: UIBarButtonItem)
{
updateUserProfile()
}
func updateUserProfile ()
{
if let userID = FIRAuth.auth()?.currentUser?.uid
{
// Note: Storage references to profile images & profile headers folder
let storageUserProfileID = Storage.storage.profile_images.child(userID)
let storageUserHeaderID = Storage.storage.profile_headers.child(userID)
guard let imageProfile = profileImage.image else { return }
guard let headerImage = headerImage.image else { return }
var storageProfileDownloadedURL: String = ""
var storageHeaderDownloadedURL: String = ""
if let newProfileImage = UIImagePNGRepresentation(imageProfile), let newHeaderImage = UIImagePNGRepresentation(headerImage)
{
storageUserProfileID.put(newProfileImage, metadata: nil, completion: { (metadata, error) in
if error != nil
{
showAlert(title: "Oops!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
// Get the URL from the storage
storageUserProfileID.downloadURL(completion: { (url, error) in
if error != nil
{
showAlert(title: "Oops!!!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
if let profileImgDownloadedURL = url?.absoluteString
{
storageProfileDownloadedURL = profileImgDownloadedURL
}
})
})
storageUserHeaderID.put(newHeaderImage, metadata: nil, completion: { (metadata, error) in
if error != nil
{
showAlert(title: "Oops!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
// Get the URL from the storage
storageUserHeaderID.downloadURL(completion: { (url, error) in
if error != nil
{
showAlert(title: "Oops!!!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
else
{
if let headerImgDownloadedURL = url?.absoluteString
{
storageHeaderDownloadedURL = headerImgDownloadedURL
}
}
})
})
//Note: Update the info for that user in Database
}
guard let newDisplayName = self.displayNameTextField.text else { return }
guard let newLocation = self.locationTextField.text else { return }
guard let newDescription = self.bioTextField.text else { return }
guard let newWebsite = self.websiteTextField.text else { return }
guard let newBirthday = self.birthdayTextField.text else { return }
let newUpdatedUserDictionary = ["imageProfile": storageProfileDownloadedURL,
"imageHeader" : storageHeaderDownloadedURL,
"description" : newDescription,
"location": newLocation,
"displayName": newDisplayName,
"website": newWebsite,
"birthday": newBirthday,
]
Database.dataService.updateUserProfile(uid: userID, user: newUpdatedUserDictionary)
print("Successfully updated!")
showAlert(title: "Profile updated", msg: "YAASS", actionButton: "OK", viewController: self)
}
}

Related

Upload Profile Pic. Cannot assign photo picked from library to profilepic in users node

func uploadProfileImage(_ image:UIImage, completion: #escaping ((_ url:URL?)->())) {
let storageRef = Storage.storage().reference().child("profileImages").child("\(NSUUID().uuidString).jpg")
guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
storageRef.putData(imageData, metadata:metaData) { (metaData, error) in
if error != nil, metaData != nil {
storageRef.downloadURL (completion: {(url, error) in
if error != nil {
if let downloadurl = url?.absoluteString {
if (self.profilePicLink == "") {
self.profilePicLink = downloadurl
Database.database().reference().child("users").child(self.uid).updateChildValues(["profilePicLink":downloadurl])
}
}
} else {
completion(nil)
}
}
)
}
}
}
I am trying to assign upload photo from my library and then assign it to profilePicLink. When I choose pic from my library, it appears in the UIImage ImageView frame, however. When I use these func and action to update avatar it went error
#IBAction func updateAvatar(_ sender: Any) {
uploadPhoto()
}
func uploadPhoto(){
selectedUser?.uploadProfileImage(imageView.image!){
url in print (URL.self)
}
}
After I check in Firestore, there is no pic stored and my profilePicLink has no value.
Please be advised on this
You are checking for error != nil, metadata != nil together. It's contradicting each other hence that condition is never executed. Change it to:
if error == nil, metadata != nil {
...
}

Why do I have Problem with upload pic in swift. Problem with nil unwrap. URL

** Update**
My point is im trying to match my profilePicLink with image I upload from my library.
Also selectedUsers is Users Type as following
var username : String = ""
var email : String = ""
var uid : String = ""
var profilePicLink : String = ""
init(username : String, email: String, uid : String, profilePicLink: String ) {
self.username = username
self.email = email
self.uid = uid
self.profilePicLink = profilePicLink
}
I am having problem when I am trying to upload photo. The action are
I pick the photo from my library
#IBAction func getPhotoButton(_ sender: Any) {
let image = UIImagePickerController()
image.delegate = self
image.sourceType = UIImagePickerController.SourceType.photoLibrary
self.present(image, animated: true, completion: nil)
}
It leads me to my photo library. After I pick my photo. I click on button "Update" with the action as following code
#IBAction func updatePhoto(_ sender: Any) {
uploadPhoto()
}
func uploadPhoto(){
selectedUser?.uploadProfileImage(imageView.image!){
url in print (URL.self)
}
}
I got the error as ** Fatal error: Unexpectedly found nil while unwrapping an Optional value: ** in the func uploadPhoto as the picture
Fatal Error
And here is the code of func in my other class (Users) for upload and get Profile Image
func getProfileImage() -> UIImage {
if let url = NSURL(string: profilePicLink){
if let data = NSData(contentsOf: url as URL) {
return UIImage(data: data as Data)!
}
}
return UIImage()
}
func uploadProfileImage(_ image:UIImage, completion: #escaping ((_ url:URL?)->())) {
guard let uid = Auth.auth().currentUser?.uid else { return }
let storageRef = Storage.storage().reference().child("user/\(uid)")
guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
storageRef.putData(imageData, metadata: metaData) { metaData, error in
if error == nil, metaData != nil {
storageRef.downloadURL { url, error in
completion(url)
// success!
}
} else {
// failed
completion(nil)
}
}
}
Updated : I modifed my function uploadProfileImage as following. My point is I wanna assign profilePicLink variables to the downloadurl. And then I update value of profilePicLink
func uploadProfileImage(_ image:UIImage, completion: #escaping ((_ url:URL?)->())) {
let storageRef = Storage.storage().reference().child("profileImages").child("\(NSUUID().uuidString).jpg")
guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
storageRef.putData(imageData, metadata:metaData) { (metaData, error) in
if error != nil, metaData != nil {
storageRef.downloadURL (completion: {(url, error) in
if error != nil {
if let downloadurl = url?.absoluteString {
if (self.profilePicLink == "") {
self.profilePicLink = downloadurl
Database.database().reference().child("users").child(self.uid).updateChildValues(["profilePicLink":downloadurl])
}
}
} else {
completion(nil)
}
}
)
}
}
}
Please be advised on this.

How to get the latest Document ID in Firestore using Swift iOS

I am giving users the possibility to set up Project-Details in an app.
Since there is also the opportunity to upload an image, i will have to make use of Firebase Storage. Because I want to give the users an overview of their projects combining the data and the uploaded picture I need to make a reference between the DocumentID in database and the ImageID in storage.
I thought about a two step approach: 1. Users are generating a Project -> User clicks "next" (document getting generated) 2. User uploads an image -> Image gets stored in Storage with Reference to the DocumentID of the just generated Project.
The goal is simply to link the Image to the Document ID of the project.
Can anyone give me a hint how to solve that problem? Here are my codes so far:
For the Project-Details:
// MARK: Store the Project Infos in Database
// Check the fields and validate that the data is correct. If everything is correct, this method returns nil. Otherwise, it returns the error message
func validateFields() -> String? {
// Check that all fields are filled in
if txtLocation.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
txtProjectTitle.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
txtProjectDescription.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
endDate.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
beginnDate.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
return "Please fill in all fields."
}
return nil
}
#IBAction func saveProject(_ sender: Any) {
let user = Auth.auth().currentUser
if let user = user {
let uid = user.uid
// Validate the fields
let error = validateFields()
if error != nil {
// There is something wrong with the fields, show error message
showError(error!)
} else {
// Create cleaned versions of the data
let projectTitle = txtProjectTitle.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let projectLocation = txtLocation.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let projectDescription = txtProjectDescription.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let projectBeginn = beginnDate.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let projectEnd = endDate.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// Save stuff
let db = Firestore.firestore()
db.collection("Projects").addDocument(data: ["Project Title": projectTitle, "Project Location": projectLocation, "Project Description": projectDescription, "Project Start": projectBeginn, "Project Finish": projectEnd, "uid": uid]) { (error) in
if error != nil {
// Show error message
self.showError("Error saving user data")
}
}
}
} else { self.showError("Please log out and log in again")}
}
// Error Handling for save
func showError(_ message:String) {
errorLabel.text = message
errorLabel.alpha = 1
}
And for the Image upload
//MARK: Imagepicker
// Image Picker functions
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
newImage.image = image
picker.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
// MARK: - Actions
// Image Picker
#IBAction func addImage(_ sender: Any) {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
let actionSheet = UIAlertController(title: "Photo Source", message: "Choose a source", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Camera", style: .default, handler: { (action: UIAlertAction) in imagePickerController.sourceType = .camera
self.present(imagePickerController, animated: true, completion: nil)
}))
actionSheet.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: { (action: UIAlertAction) in imagePickerController.sourceType = .photoLibrary
self.present(imagePickerController, animated: true, completion: nil)
}))
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(actionSheet, animated: true, completion: nil)
}
//MARK: Upload Image
#IBAction func uploadImage(_ sender: Any) {
guard let image = newImage.image, let data = image.jpegData(compressionQuality: 1.0) else {
self.showError("Something went wrong")
return
}
let imageName = UUID().uuidString
let imageReference = Storage.storage().reference()
.child("imagesFolder")
.child(imageName)
imageReference.putData(data, metadata: nil) {(metadata, err) in
if let err = err {
self.showError("Something went wrong")
return
}
imageReference.downloadURL(completion: { (url, err) in
if let err = err {
self.showError("Something went wrong")
return
}
guard let url = url else {
self.showError("Something went wrong")
return
}
let dataReference = Firestore.firestore().collection("imageReferences").document()
let documentUid = dataReference.documentID
let urlString = url.absoluteString
let imageUID = documentUid
let data = ["Image UID": imageUID, "Image URL": urlString]
dataReference.setData(data, completion: {(err) in
if let err = err {
self.showError("Something went wrong")
return
}
UserDefaults.standard.set(documentUid, forKey: imageUID)
})
})
}
}
// Error Handling for save
func showError(_ message:String) {
errorLabel.text = message
errorLabel.alpha = 1
}
So in best case I could do this even in one View Controller without the need of a two step approach. If it is not possible I would like to put the generated Document ID in
let data = ["Image UID": imageUID, "Image URL": urlString]
of
let dataReference = Firestore.firestore().collection("imageReferences").document()
let documentUid = dataReference.documentID
let urlString = url.absoluteString
let imageUID = documentUid
let data = ["Image UID": imageUID, "Image URL": urlString]
dataReference.setData(data, completion: {(err) in
if let err = err {
self.showError("Something went wrong")
return
}
I would be very pleased if someone could help me. :)

How do I update UILabels synchronously with Firestore data?

I'm currently building an iOS app that will synchronize account information from Firestore. I have the login/register process hooked up and working. However, I need help understanding how to update my logInOutBtn, fullNameTxt and emailTxt in my MenuVC automatically when an user logs in/out. Currently, it will update whenever I close then reopen the menu, but what should I use to automatically update it without having to close the menu? Thanks!
// MenuVC
override func viewDidAppear(_ animated: Bool) {
if let user = Auth.auth().currentUser , !user.isAnonymous {
// We are logged in
logInOutBtn.setTitle("Logout", for: .normal)
if UserService.userListener == nil {
UserService.getCurrentUser {
self.fullNameTxt.text = UserService.user.fullName
self.emailTxt.text = UserService.user.email
}
}
} else {
logInOutBtn.setTitle("Login", for: .normal)
self.fullNameTxt.text = "Sign in or create an account"
self.emailTxt.text = "to continue."
}
}
fileprivate func presentLoginController() {
let storyboard = UIStoryboard(name: Storyboard.LoginStoryboard, bundle: nil)
if #available(iOS 13.0, *) {
let controller = storyboard.instantiateViewController(identifier: StoryboardId.LoginVC)
present(controller, animated: true, completion: nil)
} else {
// Fallback on earlier versions
}
}
#IBAction func logInOutClicked(_ sender: Any) {
guard let user = Auth.auth().currentUser else { return }
if user.isAnonymous {
presentLoginController()
} else {
do {
try Auth.auth().signOut()
UserService.logoutUser()
Auth.auth().signInAnonymously { (result, error) in
if let error = error {
debugPrint(error)
Auth.auth().handleFireAuthError(error: error, vc: self)
}
self.presentLoginController()
}
} catch {
debugPrint(error)
Auth.auth().handleFireAuthError(error: error, vc: self)
}
}
}
// UserService
func getCurrentUser(completion: #escaping () -> ()) {
guard let authUser = auth.currentUser else { return }
let userRef = db.collection("users").document(authUser.uid)
userListener = userRef.addSnapshotListener({ (snap, error) in
if let error = error {
debugPrint(error.localizedDescription)
return
}
guard let data = snap?.data() else { return }
self.user = User.init(data: data)
completion()
})
// User Model
struct User {
var fullName: String
var address: String
var id: String
var email: String
var stripeId: String
init(fullName: String = "",
address: String = "",
id: String = "",
email: String = "",
stripeId: String = "") {
self.fullName = fullName
self.address = address
self.id = id
self.email = email
self.stripeId = stripeId
}
init(data: [String : Any]) {
fullName = data["fullName"] as? String ?? ""
address = data["address"] as? String ?? ""
id = data["id"] as? String ?? ""
email = data["email"] as? String ?? ""
stripeId = data["stripeId"] as? String ?? ""
}
static func modelToData(user: User) -> [String : Any] {
let data : [String : Any] = [
"fullName" : user.fullName,
"address" : user.address,
"id" : user.id,
"email" : user.email,
"stripeId" : user.stripeId
]
return data
}
}
// My app menu
The signout process is pretty straightforward and is marked as throws so if it fails, it will generate an error that can be handled by a catch. It is not asynchronous so it won't have (or need) a closure.
So simply stated
func signOut() {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
print("successful signout")
self.logInOutBtn.setTitle("Log In", for: .normal)
self.fullNameTxt.text = ""
self.emailTxt.text = ""
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
//present the error to the user/handle the error
}
}
The signIn function is asynchronous with a closure so when the user signs in successfully, the code in the closure will fire and that's the perfect place to update the UI.
Auth.auth().signIn(withEmail: email, password: password) { [weak self] authResult, error in
guard let strongSelf = self else { return }
// update the UI here.
}
You can also just monitor the authState with an observer and have it react to users logging in/out
self.authListener = Auth.auth()?.addAuthStateDidChangeListener { auth, user in
if let theUser = user {
print("User logged in \(theUser)") // User is signed in.
self.dismissViewControllerAnimated(true, completion: nil)
} else {
print("Need to login.") // No user is signed in.
//present login view controller
}
}
If you no longer want to observe the auth state, you can remove it with
Auth.auth()?.removeAuthStateDidChangeListener(self.authListener)

What is the correct way to log in with facebook on firebase? swift

When I log in with a facebook account in a view, I pass it a second view, in the second view I want a fetch query but in the view log I get permission denied and I dont see the info.
I have a normal firebase account, application test facebook.
this is the code view log in
#IBAction func InicioSesionFacebook(_ sender: Any)
{
esperaSesion.isHidden = false
esperaSesion.startAnimating()
let fbLoginManager = FBSDKLoginManager()
fbLoginManager.logIn(withReadPermissions: ["public_profile", "email"], from: self) { (result, error) in
if let error = error {
print("Failed to login: \(error.localizedDescription)")
self.esperaSesion.stopAnimating()
return
}
guard let accessToken = FBSDKAccessToken.current() else {
print("Failed to get access token")
self.esperaSesion.stopAnimating()
return
}
let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
// Perform login by calling Firebase APIs
Auth.auth().signIn(with: credential, completion: { (user, error) in
if let error = error
{
self.esperaSesion.stopAnimating()
print("Login error: \(error.localizedDescription)")
let alertController = UIAlertController(title: "Login Error", message: error.localizedDescription, preferredStyle: .alert)
let okayAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(okayAction)
self.present(alertController, animated: true, completion: nil)
return
}
else
{
let fbloginresult : FBSDKLoginManagerLoginResult = result!
if (result?.isCancelled)!
{
return
}
else
{
// Present the main view
self.esperaSesion.stopAnimating()
if let viewController = self.storyboard?.instantiateViewController(withIdentifier: "NavigationMasterController")
{
UIApplication.shared.keyWindow?.rootViewController = viewController
self.dismiss(animated: true, completion: nil)
}
}
}
})
}
}
this is the code in the second view, a query
import FirebaseAuth
import FirebaseDatabase
import FBSDKLoginKit
var refDB: DatabaseReference!
override func viewDidLoad()
{
super.viewDidLoad()
refDB = Database.database().reference()
CerrarSesion.layer.cornerRadius = 8
imagenPerfil.layer.cornerRadius = imagenPerfil.frame.height/2
imagenPerfil.clipsToBounds = true
verDatos()
// Do any additional setup after loading the view.
}
func verDatos()
{
let userID = Auth.auth().currentUser?.uid
refDB.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let nombre = value?["nombre"] as? String ?? ""
let apellido = value?["apellido"] as? String ?? ""
self.nombreUsuario.text = nombre
self.apellidoUsuario.text = apellido
// ...
}) { (error) in
print(error.localizedDescription)
}
}
and the button log out
#IBAction func CerrarSesion(_ sender: Any)
{
do
{
try Auth.auth().signOut()
self.view.window?.rootViewController?.dismiss(animated: true, completion: borrarUserDefaults)
}
catch let error as NSError
{
print (error.localizedDescription)
}
}
how is the correct form for log out when I logged in with facebook account?
You can check out my YouTube Tutorial on this exact topic !
https://www.youtube.com/watch?v=BfwNf-W-R4U
The version of the Facebook API that you are using is dated. The Login function should look something like this
let loginManager = LoginManager()
loginManager.logIn(readPermissions: [.publicProfile], viewController: self) {loginResult in
switch loginResult {
case .failed(let error):
print("error: \(error)")
case .cancelled:
print("User cancelled login.")
case .success(let grantedPermissions, let declinedPermissions, let accessToken):
print(grantedPermissions)
print(declinedPermissions)
fbAccessToken = accessToken
let credential = FacebookAuthProvider.credential(withAccessToken: (fbAccessToken?.authenticationToken)!)
Auth.auth().signIn(with: credential) { (user, error) in
if let error = error {
print(error)
return
}
currentUser = Auth.auth().currentUser
moveToHomeScreen()
print("Logged in!")
}
}
}
I think that you are getting a permissions error because the parameter name from the AccessToken changed and you are passing the wrong value. (Sorry I cant recall what the change was).
If you are following the Facebook API instructions on the facebook developer portal they are horrendously out of date iOS 9 I think.