Updated: 6/8/2020
In this post I’m going to show you how to user’s profile image in Firebase in iOS Swift. Google’s Firebase Realtime database allows you to store data such as text in tree like structure. Google’s Storage is another database for blog storage like images, videos, etc.
In my ProMe Sports (A social network for athletes) app this is how I retrieve user’s properties like the userid, email address, full name, profile photo URL storage location, etc. My app is built using Swift 4.2 of course so I don’t know how this would work in Objective-C.
User object model
First create a user object in a separate file.
//
// UserProfile.swift
//
// Created by Waleed on 9/10/17.
// Copyright © 2017 Waleed Sarwari. All rights reserved.
//
import Foundation
struct UserProfile {
var userId: String
var emailAddress: String
var fullName: String
var profilePhotoURL: String
// MARK: - Firebase Keys
enum UserInfoKey {
static let email = "email"
static let name = "name"
static let profilePhotoURL = "profilePhotoURL"
static let age = "age"
}
init(userId: String, fullName: String, emailAddress: String, profilePicture: String) {
self.userId = userId
self.emailAddress = emailAddress
self.fullName = fullName
self.profilePhotoURL = profilePhotoURL
}
init?(userId: String, userInfo: [String: Any]) {
let fullname = userInfo[UserInfoKey.name] as? String ?? ""
let dateJoined = userInfo[UserInfoKey.dateJoined] as? Int ?? 0
let photoURL = userInfo[UserInfoKey.photoURL] as? String ?? ""
let emailAddress = userInfo[UserInfoKey.email] as? String ?? ""
self = UserProfile(userId: userId, fullName: fullname, emailAddress: emailAddress, profilePhotoURL: profilePhotoURL)
}
}
Upload user profile
Timing and correct steps are essential to clean user experience. If you have programmed long enough you’ll know “race conditions“. In IOS I use PromiseKit to control one execution completes before another starts. Now you may not need a Promise, it all depends on what you are doing.
func uploadUserProfileImage(profileImage: UIImage) -> Promise<()> {
return Promise<()> { seal -> Void in
// Use the auto id for the image name
// Generate a unique ID for the post and prepare the post database reference
let imageStorageRef = PROFILE_IMGS_STORAGE_REF.child(Utilities.getCurrentUserId()).child("default.jpg")
// Resize the image
// let scaledImage = image.scale(newWidth: 640.0)
guard let imageData = profileImage.jpegData(compressionQuality: 0.9) else {
return
}
// Create the file metadata
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
// Upload the image to the userProfileImages storage
let uploadTask = imageStorageRef.putData(imageData, metadata: metadata, completion: {
(data, error) in
imageStorageRef.downloadURL(completion: { (url, error) in
if let uploadedImageURL = url?.absoluteString {
// Get the image url and assign to photoUrl for the current user and update
if let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest() {
changeRequest.photoURL = URL(string: uploadedImageURL)
changeRequest.commitChanges(completion: { (error) in
if let error = error {
print("Failed to change the profile image: \(error.localizedDescription)")
}else {
print("Changed user profile image")
guard let userId = Auth.auth().currentUser?.uid else {
return
}
// Save the profile of the user
let values = [UserProfile.UserInfoKey.profilePhotoURL: uploadedImageURL]
USERS_DB_REF.child(userId).updateChildValues(values, withCompletionBlock: { (error, ref) in
if error != nil {
print(error!)
return
}
print("Updated user photoUrl")
// Update cache
CacheManager.shared.cache(object: profileImage, key: userId)
seal.fulfill(())
})
}
})
}
}
})
})
uploadTask.observe(.failure) { (snapshot) in
if let error = snapshot.error {
print(error.localizedDescription)
}
}
// Observe the upload status
uploadTask.observe(.success) { (snapshot) in
}
}
}
GET USER PROFILE BY USER ID
Simplified by first getting the profile storage URL from the Firebase Realtime Database.
func getUserProfileImgURL(userId: String, completionHandler: @escaping (String) -> Void) {
// Get the rest of the user data
USERS_DB_REF.child(userId).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let userValues = snapshot.value as? NSDictionary {
if let userPhotoURL = userValues[UserProfile.UserInfoKey.profilePhotoURL] as? String {
completionHandler(userPhotoURL)
}
}
})
}
This static function can now be called from anywhere.
static func loadUserProfilePhoto(userId: String, matchingUserId: String, imageView: UIImageView) {
UserServices.shared.getUserProfileImgURL(userId: userId) { (profileImgURL) in
let imageCacheId = userId
if let image = CacheManager.shared.getFromCache(key: imageCacheId) as? UIImage {
imageView.image = image
} else {
if let url = URL(string: profileImgURL) {
let downloadTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
guard let imageData = data else {
return
}
OperationQueue.main.addOperation {
guard let image = UIImage(data: imageData) else { return }
// In the completion handler of the download task, we add a simple verification to ensure that we only display the right image. For the image that is not supposed to be displayed by the current cell, we just keep it in cache.
//
if matchingUserId == userId {
imageView.image = image
}
// Add the downloaded image to cache
CacheManager.shared.cache(object: image, key: userId)
}
})
downloadTask.resume()
}
}
}
}
You can place this static function wherever, I put it a “Utilities” class. The “matchingUserId” is for caching purposes. When the user scrolls or views the user’s profile for the first time it will download and cache in the cache manager. The second or more time during that session it will look for the cached version of the photo. This helps with speed and accuracy of loading images.
Calling the static function example
Utilities.loadUsersProfilePhoto(userId: userId, matchingUserId: self.user.object.userId, imageView: self.userImage)
This is how I retrieve the user image as data using the user Id and the photo URL location. Remember the user id comes from the Firebase Realtime Database and the actual image is stored in the Firebase Storage. This is stored in another file that does storing and retrieving only.
This is also doing some simple error checking. If there isn’t any profile image then use the default profile image that’s stored within the app assets.
Remember this all assumes the Firebase rules are allowing this, checkout this article for details. Also don’t forget to lockup your API keys!
Don’t forget to subscribe and share 🙂
Hello, I am really struggling to upload user profile pictures during signup, have them uploaded and stored, and them have them displayed across the app as needed/referenced. I just don’t know how to add the profile picture for a specific account to a UIImageView and I would really appreciate the help if you could!
There are many ways to write this code. It depends on your setup structure. I would suggest start by reading and understanding the Firebase example on how to upload files into Realtime Database. https://firebase.google.com/docs/storage/ios/upload-files#full_example
If your app is built by using just code then this tutorial is good: https://www.youtube.com/watch?v=b1vrjt7Nvb0
On my app ProMe Sports (App Store) I use an open-source framework called Eureka. This allows me to build the entire form in code and have plenty of rows. Whereas the UI builder can be extremely difficult to manage in the long run.
https://eurekacommunity.github.io/
I also had to use PromiseKit in order to control the timing of loading and uploading. You probably don’t have to.
https://github.com/mxcl/PromiseKit
Let me know if you need specific help.
Here’s a snippet of my code using Eureka:
import Eureka
…..
var mainForm: Form!
var subForm: Form!
var helpForm: Form!
var mainSection: Section!
….
mainForm = form +++
Section(header: “Personal Information”, footer: “Complete all fields.”)
<<< ImageRow() { row in row.title = "Profile Photo" row.sourceTypes = [.PhotoLibrary, .Camera] row.clearAction = .yes(style: UIAlertAction.Style.destructive) row.allowEditor = true row.useEditedImage = true row.tag = "profilePhoto" row.cell.height = ({ return 100 }) row.add(rule: RuleRequired()) } <<< TextRow() { $0.title = "Email" $0.tag = UserProfile.UserInfoKey.email $0.disabled = true $0.onCellSelection({ (TextCell, TextRow) in self.showAlertOK(title: "Read only", message: "At this time, email address cannot be changed, for more info contact support.") }) } ....... @objc func saveToDatabase() { .... // Save profile photo if let row: ImageRow = self.mainForm.rowBy(tag: "profilePhoto") { if let editedImage = row.value { _ = UserServices.shared.uploadUserProfileImage(profileImage: editedImage) // Delete current one before saving the new one // UserServices.shared.deleteCurrentUserProfileImage(userId: Utilities.getCurrentUserId()).then { // UserServices.shared.uploadUserProfileImage(profileImage: editedImage).done { // print("Done upload profile photo") // } // }.catch { (error) in // print(error) // } } else { row.value = UIImage(named: "defaultProfileImage") _ = UserServices.shared.uploadUserProfileImage(profileImage: row.value!) } } ... // loading profile image fileprivate func loadFormValues() { if let row: ImageRow = self.mainForm.rowBy(tag: "profilePhoto") { let imageCacheId = "profilePhoto\(Utilities.getCurrentUserId())" if let image = CacheManager.shared.getFromCache(key: imageCacheId) as? UIImage { row.value = image } else { UserServices.shared.getUserProfileImgByUserId(userId: Utilities.getCurrentUserId(), completionHandler: { (photo) in row.value = photo // Add the downloaded image to cache CacheManager.shared.cache(object: photo, key: imageCacheId) row.reload() }) } row.cell.accessoryView?.frame = CGRect(x: 0, y: 0, width: 85, height: 85) row.cell.accessoryView?.layer.cornerRadius = (row.cell.accessoryView?.frame.width)!/2 } } func uploadUserProfileImage(profileImage: UIImage) -> Promise<()> {
return Promise<()> { seal -> Void in
// Use the auto id for the image name
// Generate a unique ID for the post and prepare the post database reference
let imageName = Int(NSDate().timeIntervalSince1970 * 1000)
let imageStorageRef = PROFILE_IMGS_STORAGE_REF.child(Utilities.getCurrentUserId()).child(“\(imageName).jpg”)
// Resize the image
// let scaledImage = image.scale(newWidth: 640.0)
guard let imageData = profileImage.jpegData(compressionQuality: 0.9) else {
return
}
// Create the file metadata
let metadata = StorageMetadata()
metadata.contentType = “image/jpeg”
// Upload the image to the userProfileImages storage
let uploadTask = imageStorageRef.putData(imageData, metadata: metadata, completion: {
(data, error) in
imageStorageRef.downloadURL(completion: { (url, error) in
if let uploadedImageURL = url?.absoluteString {
// Get the image url and assign to photoUrl for the current user and update
if let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest() {
changeRequest.photoURL = URL(string: uploadedImageURL)
changeRequest.commitChanges(completion: { (error) in
if let error = error {
print(“Failed to change the profile image: \(error.localizedDescription)”)
}else {
print(“Changed user profile image”)
guard let userId = Auth.auth().currentUser?.uid else {
return
}
// Save the profile of the user
let values = [UserProfile.UserInfoKey.profilePhotoURL: uploadedImageURL]
USERS_DB_REF.child(userId).updateChildValues(values, withCompletionBlock: { (error, ref) in
if error != nil {
print(error!)
return
}
print(“Updated user photoUrl”)
seal.fulfill(())
})
}
})
}
}
})
})
uploadTask.observe(.failure) { (snapshot) in
if let error = snapshot.error {
print(error.localizedDescription)
}
}
// Observe the upload status
uploadTask.observe(.success) { (snapshot) in
}
}
}
This app sample also has code that uploads a profile picture: https://github.com/firebase/friendlypix-ios