Creating an app to delete non-favorited photos from the Apple Photos library directly on your iPad using the Swift Playgrounds app is an exciting project! Swift Playgrounds is a powerful tool that allows you to write and execute Swift code on your iPad without needing a Mac. However, it’s important to note that Swift Playgrounds has some limitations compared to Xcode, especially regarding full-fledged app development and access to certain system frameworks. Despite these limitations, you can still create a functional prototype to perform the desired task.
⚠️ Important Warning:
Deleting photos is irreversible and can lead to data loss. Ensure you handle deletions carefully. It’s highly recommended to backup your photos before testing this app to prevent accidental loss of important images.
Table of Contents
2. Step 1: Install and Set Up Swift Playgrounds
3. Step 2: Create a New Playground
4. Step 3: Configure App Permissions
5. Step 4: Import Necessary Frameworks
6. Step 5: Design the User Interface (UI)
7. Step 6: Implement Photo Deletion Logic
8. Step 7: Testing the Playground
9. Step 8: Final Considerations
10. Conclusion
Prerequisites
Before you begin, ensure you have the following:
1. iPad with iPadOS 14 or later: Ensure your iPad is updated to the latest iPadOS version for optimal compatibility.
2. Swift Playgrounds App Installed: Available for free on the App Store.
3. Apple Developer Account (Optional): While not strictly necessary for basic Playgrounds, having an Apple Developer account can provide additional resources and capabilities.
Step 1: Install and Set Up Swift Playgrounds
1. Download Swift Playgrounds:
• Open the App Store on your iPad.
• Search for “Swift Playgrounds†by Apple.
• Tap Get and install the app.
2. Launch Swift Playgrounds:
• Once installed, open the Swift Playgrounds app from your home screen.
3. Familiarize Yourself with the Interface:
• Explore the app to understand its layout. The main sections include Playgrounds, Learn, and Get More Playgrounds.
Step 2: Create a New Playground
1. Start a New Playground:
• In the Swift Playgrounds app, tap the â€+†icon or “Create Playgroundâ€.
2. Choose a Template:
• Select “Blank†to start from scratch.
3. Name Your Playground:
• Enter a name, e.g., PhotoCleaner, and tap “Createâ€.
4. Set Up the Environment:
• You’ll be presented with a coding area on the right and a live preview on the left (if applicable).
Step 3: Configure App Permissions
To access and modify the Photos library, your Playground needs the appropriate permissions.
1. Understand Limitations:
• Swift Playgrounds on iPad has limited capabilities compared to Xcode, especially regarding UI frameworks and certain system permissions. Some functionalities might not be fully supported.
2. Add Privacy Descriptions:
• Unlike Xcode, Swift Playgrounds does not use an Info.plist file where you typically declare privacy descriptions. Therefore, managing permissions programmatically becomes essential.
3. Request Photo Library Access in Code:
• You’ll handle permissions within your Swift code, prompting the user to grant access when the Playground runs.
Step 4: Import Necessary Frameworks
To interact with the Photos library and build a user interface, import the required frameworks.
1. Import Frameworks:
import SwiftUI
import Photos
import PlaygroundSupport
2. Enable Live View:
• At the end of your Playground, ensure you set the live view to display your SwiftUI interface.
PlaygroundPage.current.setLiveView(ContentView())
Step 5: Design the User Interface (UI)
Using SwiftUI, design a simple interface with a button to initiate the deletion process and display status messages.
1. Create ContentView Struct:
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(“Photo Cleaner”)
.font(.largeTitle)
.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 deletionInProgress {
ProgressView(“Deleting photos…”)
.progressViewStyle(CircularProgressViewStyle())
.padding()
}
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()
Button(action: {
openSettings()
}) {
Text(“Open Settings”)
.foregroundColor(.blue)
.padding()
.background(Color(UIColor.systemGray5))
.cornerRadius(8)
}
} 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()
)
}
}
}
2. Explanation of UI Components:
• Title: Displays the app name.
• Delete Button: Initiates the deletion process.
• ProgressView: Shows progress during deletion.
• Result Message: Displays success or error messages.
• Permission Denied View: Informs the user if access is denied and provides a button to open Settings.
Step 6: Implement Photo Deletion Logic
Now, implement the functions to handle photo access, fetching, and deletion.
1. Request Photo Library Access:
extension ContentView {
func requestPhotoLibraryAccess() {
PHPhotoLibrary.requestAuthorization { status in
DispatchQueue.main.async {
self.authorizationStatus = status
}
}
}
}
2. Fetch Assets to Delete:
extension ContentView {
func fetchAssetsToDelete() {
deletionInProgress = true
deletionResultMessage = “”
// Fetch all image assets
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.”
}
}
}
}
3. Perform Deletion:
extension ContentView {
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”)”
}
}
}
}
}
4. Open Settings Function:
• Since Swift Playgrounds may not support opening app settings directly, this function may have limited functionality. However, you can attempt to prompt the user to open Settings.
extension ContentView {
func openSettings() {
if let appSettings = URL(string: UIApplication.openSettingsURLString) {
if UIApplication.shared.canOpenURL(appSettings) {
UIApplication.shared.open(appSettings)
}
}
}
}
Note: On Swift Playgrounds, some functionalities like opening Settings may be restricted. Users might need to manually navigate to Settings to grant permissions.
Step 7: Testing the Playground
After implementing the UI and logic, it’s time to test your Playground to ensure it works as expected.
1. Run the Playground:
• In Swift Playgrounds, tap the Run button (▶️) to execute your code.
2. Grant Photo Library Access:
• Upon running, the app will request access to your Photos library.
• Tap “Allow Access to All Photos†when prompted.
3. Initiate Deletion:
• After authorization, tap the “Delete Non-Favorited Photos†button.
• A confirmation alert will appear. Confirm the deletion to proceed.
4. Monitor Progress and Results:
• A ProgressView will indicate that deletion is in progress.
• Upon completion, a success or error message will display.
5. Verify Deletions:
• Open the Photos app on your iPad to ensure that only non-favorited photos have been deleted.
• Remember, deleted photos move to the “Recently Deleted†album for 30 days before permanent deletion.
6. Handle Permission Denial:
• Test Scenario: Deny photo access when prompted.
• Expected Behavior: The app should inform you that access is denied and provide an option to open Settings.
Tip: To reset photo permissions, go to Settings > Privacy > Photos on your iPad and adjust permissions for Swift Playgrounds.
Step 8: Final Considerations
While Swift Playgrounds is a powerful tool for learning and prototyping, developing a fully functional app with extensive UI and system integrations is more suited to Xcode on a Mac. However, for educational purposes and simple functionalities like the one you’re implementing, Swift Playgrounds on iPad can suffice.
Limitations to Keep in Mind:
1. UI Capabilities:
• Swift Playgrounds supports SwiftUI, but some advanced UI features available in Xcode might be limited.
2. System Access:
• Certain system functionalities and permissions might be restricted or behave differently compared to a full iOS app developed in Xcode.
3. Deployment:
• Playgrounds are primarily for learning and prototyping. Deploying a Playground as a standalone app on the App Store is not feasible. For a production-level app, consider migrating your project to Xcode on a Mac.
4. Error Handling and Debugging:
• Debugging in Swift Playgrounds is less sophisticated compared to Xcode. Be prepared for more manual testing and error checking.
Enhancements for Improved Functionality:
1. User Confirmation:
• Implement multiple confirmation steps to prevent accidental deletions.
2. Progress Feedback:
• Enhance the progress indicators to provide more detailed feedback during lengthy operations.
3. Selective Deletion:
• Allow users to preview and select specific photos for deletion rather than deleting all non-favorited photos at once.
4. Undo Functionality:
• While Photos handles deletions via the “Recently Deleted†folder, consider implementing an undo feature within your app for added user convenience.
5. Backup Feature:
• Before deletion, offer users the option to backup their photos to a cloud service or another storage medium.
Conclusion
You’ve successfully created a Swift Playgrounds project on your iPad that deletes non-favorited photos from the Apple Photos library. This project demonstrates how powerful and flexible Swift Playgrounds can be for learning and prototyping Swift code directly on your iPad.
Key Takeaways:
• Swift Playgrounds is a versatile tool for coding on the go, allowing you to interact with system frameworks like Photos.
• Always prioritize user data safety by implementing confirmations and providing clear feedback.
• Testing is crucial. Ensure you thoroughly test your Playground to prevent unintended data loss.
• While Swift Playgrounds is excellent for learning and simple projects, transitioning to Xcode on a Mac is recommended for more complex app development and deployment.
Next Steps:
• Explore More SwiftUI Features: Enhance your app’s UI by exploring more SwiftUI components and modifiers.
• Learn About App Deployment: If you wish to develop full-fledged iOS apps, consider learning Xcode and the app deployment process.
• Implement Additional Features: Expand your Playground by adding features like photo previews, selective deletion, or integration with other services.
Happy coding, and always remember to handle user data responsibly!
…
Certainly! While I can’t provide actual images, I can offer a detailed step-by-step guide with code snippets and descriptions of what your app will look like in the Swift Playgrounds app on your iPad. This will help you visualize the interface and understand how each component functions.
Overview
You will create a simple SwiftUI-based interface within Swift Playgrounds that:
1. Displays a Title: “Photo Cleanerâ€.
2. Shows a Button: “Delete Non-Favorited Photosâ€.
3. Includes a Progress Indicator: Visible during the deletion process.
4. Displays Status Messages: Success or error messages post-deletion.
5. Handles Permissions: Requests access to the Photos library and guides the user if access is denied.
Step-by-Step Instructions
Step 1: Open Swift Playgrounds and Create a New Playground
1. Launch Swift Playgrounds on your iPad.
2. Create a New Playground:
• Tap the â€+†icon or “Create Playgroundâ€.
• Choose “Blank†to start from scratch.
• Name your Playground (e.g., PhotoCleaner) and tap “Createâ€.
Step 2: Set Up the Playground Environment
Ensure you have the necessary frameworks imported and set up the live view to display your SwiftUI interface.
1. Import Frameworks:
At the top of your Playground, add the following imports:
import SwiftUI
import Photos
import PlaygroundSupport
2. Set the Live View:
At the bottom of your Playground, set the live view to your main ContentView:
PlaygroundPage.current.setLiveView(ContentView())
Step 3: Design the User Interface (UI)
Create a SwiftUI ContentView that includes all necessary UI components.
1. Define ContentView Structure:
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 {
NavigationView {
VStack(spacing: 20) {
Text(“Photo Cleaner”)
.font(.largeTitle)
.fontWeight(.bold)
.padding()
if authorizationStatus == .authorized {
Button(action: {
fetchAssetsToDelete()
}) {
Text(deletionInProgress ? “Preparing to Delete…” : “Delete Non-Favorited Photos”)
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.red)
.cornerRadius(8)
}
.disabled(deletionInProgress)
if deletionInProgress {
ProgressView(“Deleting photos…”)
.progressViewStyle(CircularProgressViewStyle())
.padding()
}
if !deletionResultMessage.isEmpty {
Text(deletionResultMessage)
.foregroundColor(.green)
.padding()
}
} else if authorizationStatus == .denied || authorizationStatus == .restricted {
VStack {
Text(“Access to Photos is denied.”)
.font(.headline)
.multilineTextAlignment(.center)
.padding(.bottom, 10)
Text(“Please enable access in Settings to allow the app to delete non-favorited photos.”)
.multilineTextAlignment(.center)
.padding(.horizontal)
Button(action: {
openSettings()
}) {
Text(“Open Settings”)
.foregroundColor(.blue)
.padding()
.background(Color(UIColor.systemGray5))
.cornerRadius(8)
}
}
} else {
Text(“Requesting access to Photos…”)
.padding()
.onAppear {
requestPhotoLibraryAccess()
}
}
}
.padding()
.navigationBarTitle(“Photo Cleaner”, displayMode: .inline)
.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. Description of UI Components:
• NavigationView: Provides a navigation bar with the title “Photo Cleanerâ€.
• VStack: Arranges elements vertically with spacing.
• Title Text: Displays “Photo Cleaner†prominently.
• Delete Button: Initiates the deletion process; turns into “Preparing to Delete…†during processing.
• ProgressView: Shows a spinner with the message “Deleting photos…†when deletion is in progress.
• Result Message: Displays success or error messages after the deletion process.
• Permission Denied View: Informs the user if access is denied and provides a button to open Settings.
• Permission Request Text: Shows “Requesting access to Photos…†while awaiting user permission.
Step 4: Implement Photo Deletion Logic
Add functions to handle photo library access, fetching non-favorited photos, and performing deletions.
1. Extend ContentView with Functions:
extension ContentView {
// 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 image assets
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”)”
}
}
}
}
// Open Settings Function
func openSettings() {
if let appSettings = URL(string: UIApplication.openSettingsURLString) {
if UIApplication.shared.canOpenURL(appSettings) {
UIApplication.shared.open(appSettings)
}
}
}
}
2. Explanation of Functions:
• requestPhotoLibraryAccess(): Requests permission to access the Photos library. Updates the authorizationStatus based on user response.
• fetchAssetsToDelete():
• Fetches all image assets.
• Fetches only favorited photos.
• Identifies non-favorited photos by comparing all photos with favorited photos.
• Updates the state to show a confirmation alert if there are photos to delete.
• performDeletion(assets:):
• Deletes the specified assets using PHAssetChangeRequest.deleteAssets.
• Updates the deletionResultMessage based on the success or failure of the operation.
• openSettings(): Attempts to open the app’s settings in the Settings app to allow the user to grant photo access permissions manually.
Step 5: Set Up Live View
Ensure that your ContentView is set as the live view so that you can interact with the UI.
1. Set Live View:
At the bottom of your Playground, ensure the following line exists:
PlaygroundPage.current.setLiveView(ContentView())
This line tells Swift Playgrounds to display ContentView as the live view when you run the Playground.
Step 6: Run and Test the Playground
1. Run the Playground:
• Tap the Run button (▶️) at the top of the Playground editor.
• The live view should appear, displaying your designed UI.
2. Grant Photo Library Access:
• Upon first run, a prompt will appear requesting access to your Photos library.
• Tap: “Allow Access to All Photosâ€.
3. Interact with the App:
• Delete Button: Tap the “Delete Non-Favorited Photos†button.
• Confirmation Alert: An alert will appear asking for confirmation to delete the identified photos.
• Tap: “Delete†to proceed or “Cancel†to abort.
• Progress Indicator: While deletion is in progress, a spinner with the message “Deleting photos…†will appear.
• Result Message: After completion, a message will display indicating success or failure.
4. Verify Deletions:
• Open the Photos app on your iPad.
• Navigate to “Recently Deleted†to see the deleted photos (they remain here for 30 days before permanent deletion).
• Ensure that only non-favorited photos were deleted.
Step 7: Handle Permission Denial
1. Test Denied Permissions:
• Reset Permissions:
• Go to Settings > Privacy > Photos on your iPad.
• Find Swift Playgrounds and set access to “Neverâ€.
• Run the Playground again.
• Expected Behavior: The app should display a message informing you that access is denied and provide a button to open Settings.
2. Attempt to Open Settings:
• Tap: “Open Settings†button.
• Note: Due to Swift Playgrounds limitations, this action might not open Settings directly. If it doesn’t, manually navigate to Settings > Privacy > Photos and enable access for Swift Playgrounds.
Step 8: Final Testing and Safety Measures
1. Backup Your Photos:
• Highly Recommended: Before performing deletions, backup your photos to iCloud, a computer, or another storage solution to prevent accidental loss.
2. Test with Sample Photos:
• Create a Test Album: Add a few non-favorited photos to a separate album for safe testing.
• Run Deletion: Execute the Playground to delete these test photos.
• Verify: Ensure that only the intended photos are deleted.
3. Handle Errors Gracefully:
• Simulate Errors: Disconnect from the internet or revoke permissions mid-operation to test error handling.
• Observe Messages: Ensure that error messages are displayed appropriately.
Summary of the App’s Interface
Here’s a textual visualization of what your app’s interface will look like in Swift Playgrounds:
1. Title:
• Text: “Photo Cleanerâ€
• Font: Large, bold, centered at the top.
2. Delete Button:
• Label: “Delete Non-Favorited Photosâ€
• Appearance: Red background, white text, rounded corners.
• Behavior: Disabled and shows “Preparing to Delete…†when an operation is in progress.
3. Progress Indicator:
• Visible: Only during the deletion process.
• Appearance: Circular spinner with the label “Deleting photos…â€.
4. Status Messages:
• Success: “Successfully deleted X non-favorited photos.â€
• Failure: “Failed to delete photos: [Error Description]â€
• No Photos to Delete: “No non-favorited photos found to delete.â€
5. Permission Denied View:
• Text: Informative message about denied access.
• Button: “Open Settings†to guide the user to enable permissions.
6. Confirmation Alert:
• Title: “Confirm Deletionâ€
• Message: “Are you sure you want to delete X non-favorited photos? This action cannot be undone.â€
• Buttons:
• Delete: Initiates deletion.
• Cancel: Aborts the operation.
Best Practices and Final Tips
1. Always Backup Photos: Before running deletion operations, ensure your important photos are backed up to prevent data loss.
2. User Confirmation: Never delete photos without explicit user confirmation to maintain trust and prevent accidental deletions.
3. Handle Errors Gracefully: Always account for potential errors and provide meaningful feedback to the user.
4. Test Extensively: Use test photos to verify that your app behaves as expected without risking important data.
5. Understand Limitations: Swift Playgrounds is excellent for learning and prototyping but has limitations compared to Xcode. For more advanced features or deployment, consider transitioning to Xcode on a Mac.
6. Enhance Accessibility: Ensure your app is accessible to all users by supporting VoiceOver, dynamic type, and sufficient color contrast.
Conclusion
You’ve successfully created a SwiftUI-based app within Swift Playgrounds on your iPad that:
• Requests Access: Handles permissions to access the Photos library.
• Identifies Non-Favorited Photos: Fetches and filters photos not marked as favorites.
• Deletes Photos: Removes the identified photos upon user confirmation.
• Provides User Feedback: Displays progress indicators and status messages.
• Handles Permission Denial: Guides users to enable permissions if initially denied.
This project not only helps you manage your photo library but also deepens your understanding of SwiftUI, the Photos framework, and app development principles within Swift Playgrounds.
Happy Coding! If you have any further questions or need additional assistance, feel free to ask.