I have tried many times to follow different videos on how to upload a profile photo to firebase. Currently, Auth works and creates a user with full name, uid, & password but no image URL is stored in Firestore database nor is the actual image stored in Firestore storage.
When the user hits "sign up" I'd like the account to be created as well as the image to be stored.
Firebase Storage Rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if
request.auth != null;
}
}
}
CreateAccountAuth View:
import SwiftUI
struct CreateAccountAuth: View {
@State private var email = ""
@State private var password = ""
@State private var fullname = ""
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var viewModel: AuthViewModel
@State private var showImagePicker = false
@State private var selectedImage: UIImage?
@State private var profileImage: Image?
var body: some View {
VStack(alignment: .leading) {
Text("Let's get started")
.font(.title)
.fontWeight(.semibold)
Button {
showImagePicker.toggle()
} label: {
if let profileImage = profileImage {
profileImage
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.cornerRadius(10)
} else {
Image("EmptyProfile")
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
}
}
.padding(.bottom, 24)
.fullScreenCover(
isPresented: $showImagePicker,
onDismiss: loadImage) {
ImagePicker(image: $selectedImage)
}
VStack (alignment:.leading) {
CustomInputField(imageName: "person", placeholderText: "Name", text: $fullname)
.padding(.bottom, 24)
CustomInputField(imageName: "mail", placeholderText: "Email", text: $email)
.padding(.bottom, 24)
CustomInputField(
imageName: "lock",
placeholderText: "Password",
isSecureField: true,
text: $password)
}.padding(.bottom, 16)
VStack {
Button {
viewModel.register(
withEmail: email,
password: password,
fullname: fullname)
} label: {
Text("Sign Up")
.font(.headline)
.foregroundColor(.white)
.frame(height: 50)
.frame(maxWidth: .infinity)
.background(Color.accentColor)
.clipShape(Capsule())
.padding(.vertical)
}
Text("or")
Button {
print("Continue with phone..")
} label: {
Text("Sign up with phone")
.font(.headline)
.fontWeight(.regular)
.foregroundColor(.black)
.frame(height: 50)
.frame(maxWidth: .infinity)
.background(Color(.systemGray5))
.clipShape(Capsule())
.padding(.vertical)
}
Spacer()
Button {
presentationMode.wrappedValue.dismiss()
} label: {
HStack {
Text("Already have an account?")
.font(.footnote)
Text("Sign in")
.font(.footnote)
.fontWeight(.semibold)
}
}
.padding(.bottom, 40)
}
.padding(.top, 56)
}
.padding(.top, 40)
.navigationBarHidden(true)
.padding(.leading)
.padding(.trailing)
}
func loadImage() {
guard let selectedImage = selectedImage else { return }
profileImage = Image(uiImage: selectedImage)
}
}
struct CreateAccountAuth_Previews: PreviewProvider {
static var previews: some View {
CreateAccountAuth()
}
}
AuthViewModel View:
import SwiftUI
import FirebaseAuth
import FirebaseCore
import FirebaseStorage
import FirebaseFirestore
import FirebaseFirestoreSwift
class AuthViewModel: ObservableObject {
@Published var userSession: User?
@Published var didAuthenticateUser = false
private var tempUserSession: User?
init() {
self.userSession = Auth.auth().currentUser
print("DEBUG: User session is \(self.userSession?.uid)")
}
func login(withEmail email: String, password: String) {
Auth.auth().signIn(withEmail: email, password: password) { result, error in if let error = error {
print("DEBUG: Failed to register with error \(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
self.userSession = user
print("DEBUG: Did log user in...")
}
}
func register(withEmail email: String, password: String, fullname: String) {
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to register with error \(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
self.tempUserSession = user
print("DEBUG: Registered user successfully")
print("DEBUG: User is \(self.userSession)")
let data = ["email": email,
"fullname": fullname,
"uid": user.uid]
Firestore.firestore().collection("users")
.document(user.uid)
.setData(data) { _ in
self.didAuthenticateUser = true
}
}
}
func signOut() {
//sets user session to nil so we show login view
userSession = nil
tempUserSession = nil
didAuthenticateUser = false
//signs user out on server(backend)
try? Auth.auth().signOut()
}
func uploadProfileImage(_ image: UIImage) {
guard let uid = tempUserSession?.uid else { return }
ImageUploader.uploadImage(image: image) { profileImageUrl in
Firestore.firestore().collection("users")
.document(uid)
.updateData(["profileImageUrl": profileImageUrl]) { _ in
self.userSession = self.tempUserSession
}
}
}
}
ImageUploader:
import Firebase
import UIKit
import FirebaseStorage
struct ImageUploader {
static func uploadImage(image: UIImage, completion: @escaping(String) -> Void) {
guard let imageData = image.jpegData(compressionQuality: 0.5) else { return }
let filename = NSUUID().uuidString
let ref = Storage.storage().reference(withPath: "/profile_image/\(filename)")
ref.putData(imageData, metadata: nil) { _, error in
if let error = error {
print("DEBUG: Failed to upload image with error: \(error.localizedDescription)")
return
}
ref.downloadURL { imageUrl, _ in
guard let imageUrl = imageUrl?.absoluteString else { return }
completion(imageUrl)
}
}
}
}
CodePudding user response:
The problem is that you are not calling the uploadProfileImage function in the AuthViewModel.
In order to call it and pass the selected image, you need to either hold the image state in the view model or call the function in the view. Calling the function in the view will need a completion closure in the register function in order to wait for the completion of the user creation. This will make things pretty messy. I suggest to move the image to the view model.
class AuthViewModel: ObservableObject {
// New state for the selected image
@Published var selectedImage: UIImage?
func register(withEmail email: String, password: String, fullname: String) {
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to register with error \(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
self.tempUserSession = user
print("DEBUG: Registered user successfully")
print("DEBUG: User is \(self.userSession)")
let data = ["email": email,
"fullname": fullname,
"uid": user.uid]
Firestore.firestore().collection("users")
.document(user.uid)
.setData(data) { _ in
self.didAuthenticateUser = true
}
// Call your upload profile image function here.
self.uploadProfileImage(selectedImage)
}
}
}
You need to change the ImagePicker.
ImagePicker(image: $viewModel.selectedImage)
Also the loadImage function.
func loadImage() {
guard let selectedImage = viewModel.selectedImage else { return }
profileImage = Image(uiImage: selectedImage)
}