Make me a swift app: How to delete photos from Apple photos library which aren’t favorited

Creating a Swift app to delete non-favorited photos from the Apple Photos library involves several steps, including requesting the necessary permissions, accessing the Photos library, identifying non-favorited photos, and performing the deletion. Below is a comprehensive guide to help you build this app using Swift and the Photos framework.

**⚠️ **Important Note: Deleting photos is irreversible and can lead to data loss. Ensure you handle deletions carefully, provide appropriate warnings to users, and possibly implement a backup or confirmation step before deleting.

Prerequisites

1. Xcode Installed: Ensure you have the latest version of Xcode installed.

2. SwiftUI or UIKit Knowledge: This guide uses SwiftUI for simplicity, but you can adapt it to UIKit if preferred.

3. Apple Developer Account: Required for testing on physical devices, especially for accessing the Photos library.

Step 1: Create a New SwiftUI Project

1. Open Xcode and select File > New > Project.

2. Choose App under the iOS section and click Next.

3. Name your project (e.g., DeleteNonFavoritedPhotos), select Swift as the language, and SwiftUI as the interface. Click Next and save the project.

Step 2: Configure App Permissions

To access and modify the Photos library, you need to request the appropriate permissions.

1. Open your project’s Info.plist file.

2. Add the following keys with appropriate descriptions:

• Privacy – Photo Library Usage Description (NSPhotoLibraryUsageDescription): Explain why your app needs access to the Photos library.

• Privacy – Photo Library Additions Usage Description (NSPhotoLibraryAddUsageDescription): If your app adds photos, provide a description. While not strictly necessary for deletion, it’s good practice.

<key>NSPhotoLibraryUsageDescription</key>

<string>This app requires access to your photo library to delete non-favorited photos.</string>

<key>NSPhotoLibraryAddUsageDescription</key>

<string>This app may add photos to your library.</string>

Step 3: Import the Photos Framework

In your SwiftUI view (e.g., ContentView.swift), import the Photos framework:

import SwiftUI

import Photos

Step 4: Implement the Photo Deletion Logic

Below is a complete SwiftUI example that:

1. Requests authorization to access the Photos library.

2. Fetches all photos and identifies those that are not marked as favorites.

3. Deletes the non-favorited photos upon user confirmation.

import SwiftUI

import Photos

struct ContentView: View {

    @State private var authorizationStatus: PHAuthorizationStatus = .notDetermined

    @State private var deletionInProgress = false

    @State private var deletionResultMessage = “”

    var body: some View {

        VStack(spacing: 20) {

            Text(“Delete Non-Favorited Photos”)

                .font(.title)

                .padding()

            if authorizationStatus == .authorized {

                Button(action: {

                    deleteNonFavoritedPhotos()

                }) {

                    Text(deletionInProgress ? “Deleting…” : “Delete Non-Favorited Photos”)

                        .foregroundColor(.white)

                        .padding()

                        .background(Color.red)

                        .cornerRadius(8)

                }

                .disabled(deletionInProgress)

                if !deletionResultMessage.isEmpty {

                    Text(deletionResultMessage)

                        .foregroundColor(.green)

                        .padding()

                }

            } else if authorizationStatus == .denied || authorizationStatus == .restricted {

                Text(“Access to Photos is denied. Please enable it in Settings.”)

                    .multilineTextAlignment(.center)

                    .padding()

            } else {

                Text(“Requesting access to Photos…”)

                    .padding()

                    .onAppear {

                        requestPhotoLibraryAccess()

                    }

            }

        }

        .padding()

    }

    // Request Photo Library Access

    func requestPhotoLibraryAccess() {

        PHPhotoLibrary.requestAuthorization { status in

            DispatchQueue.main.async {

                self.authorizationStatus = status

            }

        }

    }

    // Delete Non-Favorited Photos

    func deleteNonFavoritedPhotos() {

        deletionInProgress = true

        deletionResultMessage = “”

        // Fetch all photos

        let allPhotosOptions = PHFetchOptions()

        allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: “creationDate”, ascending: true)]

        let allPhotos = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)

        // Fetch favorited photos

        let favoritedOptions = PHFetchOptions()

        favoritedOptions.predicate = NSPredicate(format: “favorite == YES”)

        let favoritedPhotos = PHAsset.fetchAssets(with: .image, options: favoritedOptions)

        // Create a Set of favorited photo IDs for quick lookup

        var favoritedPhotoIDs = Set<String>()

        favoritedPhotos.enumerateObjects { (asset, _, _) in

            favoritedPhotoIDs.insert(asset.localIdentifier)

        }

        // Identify non-favorited photos

        var assetsToDelete = [PHAsset]()

        allPhotos.enumerateObjects { (asset, _, _) in

            if !favoritedPhotoIDs.contains(asset.localIdentifier) {

                assetsToDelete.append(asset)

            }

        }

        // Confirm deletion with the user

        DispatchQueue.main.async {

            // Optional: Add a confirmation alert here

            performDeletion(assets: assetsToDelete)

        }

    }

    // Perform Deletion

    func performDeletion(assets: [PHAsset]) {

        guard !assets.isEmpty else {

            deletionInProgress = false

            deletionResultMessage = “No non-favorited photos found to delete.”

            return

        }

        PHPhotoLibrary.shared().performChanges({

            PHAssetChangeRequest.deleteAssets(assets as NSFastEnumeration)

        }) { success, error in

            DispatchQueue.main.async {

                deletionInProgress = false

                if success {

                    deletionResultMessage = “Successfully deleted \(assets.count) non-favorited photos.”

                } else {

                    deletionResultMessage = “Failed to delete photos: \(error?.localizedDescription ?? “Unknown error”)”

                }

            }

        }

    }

}

Step 5: Understanding the Code

1. Requesting Authorization:

• The app requests access to the user’s Photos library using PHPhotoLibrary.requestAuthorization.

• The UI updates based on the authorization status.

2. Fetching Photos:

• All Photos: Fetched using PHAsset.fetchAssets(with: .image, options: allPhotosOptions).

• Favorited Photos: Fetched using a predicate that filters assets where favorite == YES.

3. Identifying Non-Favorited Photos:

• By creating a Set of favorited photo IDs, the app efficiently checks which photos are not in this set.

4. Deleting Photos:

• PHPhotoLibrary.shared().performChanges is used to delete the identified non-favorited photos.

• The user is informed about the success or failure of the operation.

Step 6: Enhancements and Best Practices

1. User Confirmation:

• Before performing deletions, it’s advisable to present a confirmation dialog to the user to prevent accidental data loss.

// Example confirmation dialog

@State private var showConfirmation = false

@State private var assetsToDelete: [PHAsset] = []

// Modify deleteNonFavoritedPhotos

func deleteNonFavoritedPhotos() {

    // … [previous code to fetch assetsToDelete]

    self.assetsToDelete = assetsToDelete

    if !assetsToDelete.isEmpty {

        showConfirmation = true

    } else {

        deletionResultMessage = “No non-favorited photos found to delete.”

    }

}

// In the body, add a confirmation alert

.alert(isPresented: $showConfirmation) {

    Alert(

        title: Text(“Confirm Deletion”),

        message: Text(“Are you sure you want to delete \(assetsToDelete.count) non-favorited photos? This action cannot be undone.”),

        primaryButton: .destructive(Text(“Delete”)) {

            performDeletion(assets: assetsToDelete)

        },

        secondaryButton: .cancel()

    )

}

2. Handling Different Media Types:

• The example focuses on images (.image). To include videos or other media types, adjust the PHAsset.fetchAssets parameters accordingly.

3. Performance Considerations:

• For large photo libraries, consider implementing progress indicators or batching deletions to enhance performance and user experience.

4. Error Handling:

• Enhance error handling to cover different failure scenarios, such as insufficient permissions or network issues (if using iCloud Photos).

5. User Feedback:

• Provide more detailed feedback, such as the names of deleted photos or allowing users to review deletions before they occur.

Step 7: Testing the App

1. Simulator Limitations:

• The iOS Simulator has a limited Photos library. For thorough testing, use a physical device with a populated Photos library.

2. Running the App:

• Connect your iPhone or iPad.

• Select your device as the run destination in Xcode.

• Click the Run button to build and install the app on your device.

• Grant the necessary permissions when prompted.

3. Verify Deletions:

• After running the app, check the Photos app to ensure that only non-favorited photos were deleted.

Full Code Example

Here’s the complete ContentView.swift for reference:

import SwiftUI

import Photos

struct ContentView: View {

    @State private var authorizationStatus: PHAuthorizationStatus = .notDetermined

    @State private var deletionInProgress = false

    @State private var deletionResultMessage = “”

    @State private var showConfirmation = false

    @State private var assetsToDelete: [PHAsset] = []

    var body: some View {

        VStack(spacing: 20) {

            Text(“Delete Non-Favorited Photos”)

                .font(.title)

                .padding()

            if authorizationStatus == .authorized {

                Button(action: {

                    fetchAssetsToDelete()

                }) {

                    Text(deletionInProgress ? “Preparing to Delete…” : “Delete Non-Favorited Photos”)

                        .foregroundColor(.white)

                        .padding()

                        .background(Color.red)

                        .cornerRadius(8)

                }

                .disabled(deletionInProgress)

                if !deletionResultMessage.isEmpty {

                    Text(deletionResultMessage)

                        .foregroundColor(.green)

                        .padding()

                }

            } else if authorizationStatus == .denied || authorizationStatus == .restricted {

                Text(“Access to Photos is denied. Please enable it in Settings.”)

                    .multilineTextAlignment(.center)

                    .padding()

            } else {

                Text(“Requesting access to Photos…”)

                    .padding()

                    .onAppear {

                        requestPhotoLibraryAccess()

                    }

            }

        }

        .padding()

        .alert(isPresented: $showConfirmation) {

            Alert(

                title: Text(“Confirm Deletion”),

                message: Text(“Are you sure you want to delete \(assetsToDelete.count) non-favorited photos? This action cannot be undone.”),

                primaryButton: .destructive(Text(“Delete”)) {

                    performDeletion(assets: assetsToDelete)

                },

                secondaryButton: .cancel()

            )

        }

    }

    // Request Photo Library Access

    func requestPhotoLibraryAccess() {

        PHPhotoLibrary.requestAuthorization { status in

            DispatchQueue.main.async {

                self.authorizationStatus = status

            }

        }

    }

    // Fetch Assets to Delete

    func fetchAssetsToDelete() {

        deletionInProgress = true

        deletionResultMessage = “”

        // Fetch all photos

        let allPhotosOptions = PHFetchOptions()

        allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: “creationDate”, ascending: true)]

        let allPhotos = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)

        // Fetch favorited photos

        let favoritedOptions = PHFetchOptions()

        favoritedOptions.predicate = NSPredicate(format: “favorite == YES”)

        let favoritedPhotos = PHAsset.fetchAssets(with: .image, options: favoritedOptions)

        // Create a Set of favorited photo IDs for quick lookup

        var favoritedPhotoIDs = Set<String>()

        favoritedPhotos.enumerateObjects { (asset, _, _) in

            favoritedPhotoIDs.insert(asset.localIdentifier)

        }

        // Identify non-favorited photos

        var assetsToDeleteTemp = [PHAsset]()

        allPhotos.enumerateObjects { (asset, _, _) in

            if !favoritedPhotoIDs.contains(asset.localIdentifier) {

                assetsToDeleteTemp.append(asset)

            }

        }

        // Update state and show confirmation

        DispatchQueue.main.async {

            self.assetsToDelete = assetsToDeleteTemp

            self.deletionInProgress = false

            if !assetsToDeleteTemp.isEmpty {

                self.showConfirmation = true

            } else {

                self.deletionResultMessage = “No non-favorited photos found to delete.”

            }

        }

    }

    // Perform Deletion

    func performDeletion(assets: [PHAsset]) {

        deletionInProgress = true

        deletionResultMessage = “”

        PHPhotoLibrary.shared().performChanges({

            PHAssetChangeRequest.deleteAssets(assets as NSFastEnumeration)

        }) { success, error in

            DispatchQueue.main.async {

                deletionInProgress = false

                if success {

                    deletionResultMessage = “Successfully deleted \(assets.count) non-favorited photos.”

                } else {

                    deletionResultMessage = “Failed to delete photos: \(error?.localizedDescription ?? “Unknown error”)”

                }

            }

        }

    }

}

Conclusion

By following the steps above, you can create a Swift app that programmatically deletes non-favorited photos from the Apple Photos library. Always ensure that users are well-informed about the actions your app performs, especially when dealing with sensitive operations like deleting data. Implement additional safeguards as needed to enhance user trust and data safety.