Profile Photo Selection from Library

Complete guide to selecting profile photos from device library and displaying them in UIImageView

Profile Photo Selection

Note:

This tutorial covers implementing profile photo selection from the device library and displaying the selected image in a UIImageView. This is a common feature in iOS apps for user profile customization.

Overview

Hello!

In this guide, we will implement functionality that allows users to select an image from their library when they tap on a default profile photo, and then display the selected image in a UIImageView.

Reference:

Step 1: Making the Photo Tappable

First, add a UIImageView to your Storyboard and connect it to your Swift file by giving it an ID using control or right-click.

UIImageView Connection

To make the image we created interactive for user interaction, let's define the following code in the viewDidLoad function:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Make the image view tappable
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
    profileImageView.isUserInteractionEnabled = true
    profileImageView.addGestureRecognizer(tapGesture)
}

@objc func imageTapped() {
    // Handle tap action
    selectImageFromLibrary()
}

The #selector part requires an Objective-C function. This function specifies what to do when tapped with (imageTapped).

Step 2: Photo Selection from Library

For photo selection from the library, we need to call two libraries in our class: UIImagePickerControllerDelegate and UINavigationControllerDelegate.

import UIKit

class ProfileViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    @IBOutlet weak var profileImageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupProfileImage()
    }
    
    private func setupProfileImage() {
        // Make the image view tappable
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
        profileImageView.isUserInteractionEnabled = true
        profileImageView.addGestureRecognizer(tapGesture)
        
        // Set default profile image
        profileImageView.image = UIImage(named: "default_profile")
        profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
        profileImageView.clipsToBounds = true
    }
    
    @objc func imageTapped() {
        selectImageFromLibrary()
    }
}

Assign UIImagePickerController to a variable and specify that it will be taken from the library in the specified view. Let's make it happen with present.

private func selectImageFromLibrary() {
    let imagePickerController = UIImagePickerController()
    imagePickerController.delegate = self
    imagePickerController.sourceType = .photoLibrary
    imagePickerController.allowsEditing = true
    
    present(imagePickerController, animated: true, completion: nil)
}

Step 3: Displaying Selected Photo in UIImageView

After the user makes a selection, we need to display the tapped photo in the UIImageView. For this, you can call the function by typing didFinishPickingMediaWithInfo in an empty code block.

This function defines what to do after the selection. We can perform the operation by specifying the image in the code block and the original photo in the info section. We can write dismiss to close the function.

The code we created will look like the following in the final stage:

// MARK: - UIImagePickerControllerDelegate

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    
    if let selectedImage = info[.editedImage] as? UIImage {
        // Use edited image if available
        profileImageView.image = selectedImage
    } else if let originalImage = info[.originalImage] as? UIImage {
        // Use original image if no edited version
        profileImageView.image = originalImage
    }
    
    // Save the selected image to UserDefaults or Firebase Storage
    saveProfileImage()
    
    // Dismiss the picker
    picker.dismiss(animated: true, completion: nil)
}

func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    picker.dismiss(animated: true, completion: nil)
}

Step 4: Complete Implementation

Here's the complete implementation for profile photo selection:

import UIKit

class ProfileViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    @IBOutlet weak var profileImageView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupProfileImage()
        loadUserProfile()
    }
    
    private func setupProfileImage() {
        // Make the image view tappable
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
        profileImageView.isUserInteractionEnabled = true
        profileImageView.addGestureRecognizer(tapGesture)
        
        // Configure image view appearance
        profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
        profileImageView.clipsToBounds = true
        profileImageView.layer.borderWidth = 2
        profileImageView.layer.borderColor = UIColor.systemBlue.cgColor
        
        // Set default image
        if let savedImage = loadSavedProfileImage() {
            profileImageView.image = savedImage
        } else {
            profileImageView.image = UIImage(named: "default_profile")
        }
    }
    
    @objc func imageTapped() {
        showImagePickerOptions()
    }
    
    private func showImagePickerOptions() {
        let alertController = UIAlertController(title: "Select Photo", message: "Choose a photo source", preferredStyle: .actionSheet)
        
        let cameraAction = UIAlertAction(title: "Camera", style: .default) { _ in
            self.selectImageFromCamera()
        }
        
        let libraryAction = UIAlertAction(title: "Photo Library", style: .default) { _ in
            self.selectImageFromLibrary()
        }
        
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        
        alertController.addAction(cameraAction)
        alertController.addAction(libraryAction)
        alertController.addAction(cancelAction)
        
        present(alertController, animated: true, completion: nil)
    }
    
    private func selectImageFromCamera() {
        if UIImagePickerController.isSourceTypeAvailable(.camera) {
            let imagePickerController = UIImagePickerController()
            imagePickerController.delegate = self
            imagePickerController.sourceType = .camera
            imagePickerController.allowsEditing = true
            
            present(imagePickerController, animated: true, completion: nil)
        } else {
            showAlert(title: "Camera Not Available", message: "Camera is not available on this device")
        }
    }
    
    private func selectImageFromLibrary() {
        let imagePickerController = UIImagePickerController()
        imagePickerController.delegate = self
        imagePickerController.sourceType = .photoLibrary
        imagePickerController.allowsEditing = true
        
        present(imagePickerController, animated: true, completion: nil)
    }
    
    // MARK: - UIImagePickerControllerDelegate
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
        if let selectedImage = info[.editedImage] as? UIImage {
            // Use edited image if available
            profileImageView.image = selectedImage
        } else if let originalImage = info[.originalImage] as? UIImage {
            // Use original image if no edited version
            profileImageView.image = originalImage
        }
        
        // Save the selected image
        saveProfileImage()
        
        // Dismiss the picker
        picker.dismiss(animated: true, completion: nil)
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
    
    // MARK: - Image Storage
    
    private func saveProfileImage() {
        guard let image = profileImageView.image else { return }
        
        // Save to UserDefaults (for demo purposes)
        if let imageData = image.jpegData(compressionQuality: 0.8) {
            UserDefaults.standard.set(imageData, forKey: "profileImage")
        }
        
        // TODO: Upload to Firebase Storage
        uploadImageToFirebase(image)
    }
    
    private func loadSavedProfileImage() -> UIImage? {
        if let imageData = UserDefaults.standard.data(forKey: "profileImage") {
            return UIImage(data: imageData)
        }
        return nil
    }
    
    private func uploadImageToFirebase(_ image: UIImage) {
        // TODO: Implement Firebase Storage upload
        // This would typically involve:
        // 1. Convert image to Data
        // 2. Upload to Firebase Storage
        // 3. Get download URL
        // 4. Save URL to Firestore
    }
    
    // MARK: - User Profile
    
    private func loadUserProfile() {
        // Load user information
        if let user = Auth.auth().currentUser {
            nameLabel.text = user.displayName ?? "User"
            emailLabel.text = user.email
        }
    }
    
    private func showAlert(title: String, message: String) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }
}

Step 5: Enhanced Features

Image Compression and Optimization

private func compressImage(_ image: UIImage) -> Data? {
    let maxSize: CGFloat = 1024 // Maximum dimension
    let scale = min(maxSize / image.size.width, maxSize / image.size.height)
    
    let newSize = CGSize(width: image.size.width * scale, height: image.size.height * scale)
    
    UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
    image.draw(in: CGRect(origin: .zero, size: newSize))
    let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return resizedImage?.jpegData(compressionQuality: 0.8)
}

Firebase Storage Integration

import FirebaseStorage

private func uploadImageToFirebase(_ image: UIImage) {
    guard let imageData = compressImage(image) else { return }
    
    let storageRef = Storage.storage().reference()
    let imageRef = storageRef.child("profile_images/\(Auth.auth().currentUser?.uid ?? "unknown").jpg")
    
    let uploadTask = imageRef.putData(imageData, metadata: nil) { metadata, error in
        if let error = error {
            print("Error uploading image: \(error)")
            return
        }
        
        // Get download URL
        imageRef.downloadURL { url, error in
            if let downloadURL = url {
                self.saveProfileImageURL(downloadURL.absoluteString)
            }
        }
    }
    
    // Show upload progress
    uploadTask.observe(.progress) { snapshot in
        let progress = Double(snapshot.progress!.completedUnitCount) / Double(snapshot.progress!.totalUnitCount)
        print("Upload progress: \(progress * 100)%")
    }
}

private func saveProfileImageURL(_ url: String) {
    // Save to Firestore
    if let userID = Auth.auth().currentUser?.uid {
        let db = Firestore.firestore()
        db.collection("users").document(userID).updateData([
            "profileImageURL": url
        ]) { error in
            if let error = error {
                print("Error saving profile image URL: \(error)")
            } else {
                print("Profile image URL saved successfully")
            }
        }
    }
}

Image Filters and Effects

private func applyImageFilter(_ image: UIImage) -> UIImage {
    let context = CIContext()
    let ciImage = CIImage(image: image)
    
    // Apply a simple filter (you can customize this)
    let filter = CIFilter(name: "CIPhotoEffectNoir")
    filter?.setValue(ciImage, forKey: kCIInputImageKey)
    
    if let outputImage = filter?.outputImage,
       let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
        return UIImage(cgImage: cgImage)
    }
    
    return image
}

Step 6: Permissions

Camera and Photo Library Permissions

Add these keys to your Info.plist:

<key>NSCameraUsageDescription</key>
<string>This app needs access to camera to take profile photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photo library to select profile photos.</string>

Check and Request Permissions

import Photos

private func checkPhotoLibraryPermission() {
    let status = PHPhotoLibrary.authorizationStatus()
    
    switch status {
    case .authorized, .limited:
        selectImageFromLibrary()
    case .denied, .restricted:
        showPermissionAlert()
    case .notDetermined:
        PHPhotoLibrary.requestAuthorization { status in
            DispatchQueue.main.async {
                if status == .authorized || status == .limited {
                    self.selectImageFromLibrary()
                } else {
                    self.showPermissionAlert()
                }
            }
        }
    @unknown default:
        showPermissionAlert()
    }
}

private func showPermissionAlert() {
    let alert = UIAlertController(
        title: "Permission Required",
        message: "Please enable photo library access in Settings to select profile photos.",
        preferredStyle: .alert
    )
    
    let settingsAction = UIAlertAction(title: "Settings", style: .default) { _ in
        if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
            UIApplication.shared.open(settingsURL)
        }
    }
    
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    
    alert.addAction(settingsAction)
    alert.addAction(cancelAction)
    
    present(alert, animated: true, completion: nil)
}

Step 7: Best Practices

Performance Optimization

  1. Image Compression: Always compress images before storage
  2. Memory Management: Dispose of large images properly
  3. Caching: Cache images locally for better performance
  4. Background Processing: Handle image processing in background

User Experience

  1. Loading States: Show progress during image upload
  2. Error Handling: Provide clear error messages
  3. Image Validation: Check image size and format
  4. Fallback Images: Provide default images when needed

Troubleshooting

Common Issues:

  1. "Permission denied" - Check Info.plist permissions
  2. "Image not displaying" - Verify UIImageView connections
  3. "Memory issues" - Implement proper image compression
  4. "Upload failures" - Check Firebase configuration

Note:

Profile photo selection is a common feature that enhances user experience. Remember to handle permissions properly, compress images for performance, and provide clear feedback to users during the process.