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.