{"id":643437,"date":"2024-10-22T19:14:02","date_gmt":"2024-10-22T23:14:02","guid":{"rendered":"https:\/\/erickimphotography.com\/blog\/?p=643437"},"modified":"2024-10-22T19:15:37","modified_gmt":"2024-10-22T23:15:37","slug":"how-to-program-it-only-using-ipad-and-swift-playgrounds-app","status":"publish","type":"post","link":"https:\/\/erickimphotography.com\/blog\/2024\/10\/22\/how-to-program-it-only-using-ipad-and-swift-playgrounds-app\/","title":{"rendered":"How to program it only using iPad and swift playgrounds app"},"content":{"rendered":"\n<p>Creating an app to delete non-favorited photos from the Apple Photos library <strong>directly on your iPad<\/strong> using the <strong>Swift Playgrounds<\/strong> 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\u00e2\u20ac\u2122s important to note that <strong>Swift Playgrounds<\/strong> has some limitations compared to <strong>Xcode<\/strong>, 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.<\/p>\n\n\n\n<p><strong>&#x26a0;&#xfe0f; Important Warning:<\/strong><\/p>\n\n\n\n<p><strong>Deleting photos is irreversible and can lead to data loss.<\/strong> Ensure you handle deletions carefully. It\u00e2\u20ac\u2122s highly recommended to <strong>backup your photos<\/strong> before testing this app to prevent accidental loss of important images.<\/p>\n\n\n\n<p><strong>Table of Contents<\/strong><\/p>\n\n\n\n<p>1. <a href=\"#prerequisites\">Prerequisites<\/a><\/p>\n\n\n\n<p>2. <a href=\"#step-1-install-and-set-up-swift-playgrounds\">Step 1: Install and Set Up Swift Playgrounds<\/a><\/p>\n\n\n\n<p>3. <a href=\"#step-2-create-a-new-playground\">Step 2: Create a New Playground<\/a><\/p>\n\n\n\n<p>4. <a href=\"#step-3-configure-app-permissions\">Step 3: Configure App Permissions<\/a><\/p>\n\n\n\n<p>5. <a href=\"#step-4-import-necessary-frameworks\">Step 4: Import Necessary Frameworks<\/a><\/p>\n\n\n\n<p>6. <a href=\"#step-5-design-the-user-interface-ui\">Step 5: Design the User Interface (UI)<\/a><\/p>\n\n\n\n<p>7. <a href=\"#step-6-implement-photo-deletion-logic\">Step 6: Implement Photo Deletion Logic<\/a><\/p>\n\n\n\n<p>8. <a href=\"#step-7-testing-the-playground\">Step 7: Testing the Playground<\/a><\/p>\n\n\n\n<p>9. <a href=\"#step-8-final-considerations\">Step 8: Final Considerations<\/a><\/p>\n\n\n\n<p>10. <a href=\"#conclusion\">Conclusion<\/a><\/p>\n\n\n\n<p><strong>Prerequisites<\/strong><\/p>\n\n\n\n<p>Before you begin, ensure you have the following:<\/p>\n\n\n\n<p>1. <strong>iPad with iPadOS 14 or later<\/strong>: Ensure your iPad is updated to the latest iPadOS version for optimal compatibility.<\/p>\n\n\n\n<p>2. <strong>Swift Playgrounds App Installed<\/strong>: Available for free on the <a href=\"https:\/\/apps.apple.com\/us\/app\/swift-playgrounds\/id908519492\">App Store<\/a>.<\/p>\n\n\n\n<p>3. <strong>Apple Developer Account (Optional)<\/strong>: While not strictly necessary for basic Playgrounds, having an Apple Developer account can provide additional resources and capabilities.<\/p>\n\n\n\n<p><strong>Step 1: Install and Set Up Swift Playgrounds<\/strong><\/p>\n\n\n\n<p>1. <strong>Download Swift Playgrounds<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Open the <strong>App Store<\/strong> on your iPad.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Search for \u00e2\u20ac\u0153<strong>Swift Playgrounds<\/strong>\u00e2\u20ac\u009d by Apple.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Tap <strong>Get<\/strong> and install the app.<\/p>\n\n\n\n<p>2. <strong>Launch Swift Playgrounds<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Once installed, open the <strong>Swift Playgrounds<\/strong> app from your home screen.<\/p>\n\n\n\n<p>3. <strong>Familiarize Yourself with the Interface<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Explore the app to understand its layout. The main sections include <strong>Playgrounds<\/strong>, <strong>Learn<\/strong>, and <strong>Get More Playgrounds<\/strong>.<\/p>\n\n\n\n<p><strong>Step 2: Create a New Playground<\/strong><\/p>\n\n\n\n<p>1. <strong>Start a New Playground<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 In the <strong>Swift Playgrounds<\/strong> app, tap the <strong>\u00e2\u20ac\u009d+\u00e2\u20ac\u009d<\/strong> icon or <strong>\u00e2\u20ac\u0153Create Playground\u00e2\u20ac\u009d<\/strong>.<\/p>\n\n\n\n<p>2. <strong>Choose a Template<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Select <strong>\u00e2\u20ac\u0153Blank\u00e2\u20ac\u009d<\/strong> to start from scratch.<\/p>\n\n\n\n<p>3. <strong>Name Your Playground<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Enter a name, e.g., PhotoCleaner, and tap <strong>\u00e2\u20ac\u0153Create\u00e2\u20ac\u009d<\/strong>.<\/p>\n\n\n\n<p>4. <strong>Set Up the Environment<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 You\u00e2\u20ac\u2122ll be presented with a coding area on the right and a live preview on the left (if applicable).<\/p>\n\n\n\n<p><strong>Step 3: Configure App Permissions<\/strong><\/p>\n\n\n\n<p>To access and modify the Photos library, your Playground needs the appropriate permissions.<\/p>\n\n\n\n<p>1. <strong>Understand Limitations<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Swift Playgrounds<\/strong> on iPad has limited capabilities compared to <strong>Xcode<\/strong>, especially regarding UI frameworks and certain system permissions. Some functionalities might not be fully supported.<\/p>\n\n\n\n<p>2. <strong>Add Privacy Descriptions<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Unlike <strong>Xcode<\/strong>, <strong>Swift Playgrounds<\/strong> does not use an Info.plist file where you typically declare privacy descriptions. Therefore, managing permissions programmatically becomes essential.<\/p>\n\n\n\n<p>3. <strong>Request Photo Library Access in Code<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 You\u00e2\u20ac\u2122ll handle permissions within your Swift code, prompting the user to grant access when the Playground runs.<\/p>\n\n\n\n<p><strong>Step 4: Import Necessary Frameworks<\/strong><\/p>\n\n\n\n<p>To interact with the Photos library and build a user interface, import the required frameworks.<\/p>\n\n\n\n<p>1. <strong>Import Frameworks<\/strong>:<\/p>\n\n\n\n<p>import SwiftUI<\/p>\n\n\n\n<p>import Photos<\/p>\n\n\n\n<p>import PlaygroundSupport<\/p>\n\n\n\n<p>2. <strong>Enable Live View<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 At the end of your Playground, ensure you set the live view to display your SwiftUI interface.<\/p>\n\n\n\n<p>PlaygroundPage.current.setLiveView(ContentView())<\/p>\n\n\n\n<p><strong>Step 5: Design the User Interface (UI)<\/strong><\/p>\n\n\n\n<p>Using <strong>SwiftUI<\/strong>, design a simple interface with a button to initiate the deletion process and display status messages.<\/p>\n\n\n\n<p>1. <strong>Create ContentView Struct<\/strong>:<\/p>\n\n\n\n<p>struct ContentView: View {<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var authorizationStatus: PHAuthorizationStatus = .notDetermined<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var deletionInProgress = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var deletionResultMessage = &#8220;&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var showConfirmation = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var assetsToDelete: [PHAsset] = []<\/p>\n\n\n\n<p>&nbsp; &nbsp; var body: some View {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; VStack(spacing: 20) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Photo Cleaner&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .font(.largeTitle)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if authorizationStatus == .authorized {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Button(action: {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fetchAssetsToDelete()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(deletionInProgress ? &#8220;Preparing to Delete&#8230;&#8221; : &#8220;Delete Non-Favorited Photos&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .foregroundColor(.white)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .background(Color.red)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .cornerRadius(8)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .disabled(deletionInProgress)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if deletionInProgress {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ProgressView(&#8220;Deleting photos&#8230;&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .progressViewStyle(CircularProgressViewStyle())<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if !deletionResultMessage.isEmpty {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(deletionResultMessage)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .foregroundColor(.green)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if authorizationStatus == .denied || authorizationStatus == .restricted {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Access to Photos is denied. Please enable it in Settings.&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .multilineTextAlignment(.center)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Button(action: {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; openSettings()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Open Settings&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .foregroundColor(.blue)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .background(Color(UIColor.systemGray5))<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .cornerRadius(8)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Requesting access to Photos&#8230;&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .onAppear {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; requestPhotoLibraryAccess()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; .alert(isPresented: $showConfirmation) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Alert(<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title: Text(&#8220;Confirm Deletion&#8221;),<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message: Text(&#8220;Are you sure you want to delete \\(assetsToDelete.count) non-favorited photos? This action cannot be undone.&#8221;),<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; primaryButton: .destructive(Text(&#8220;Delete&#8221;)) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; performDeletion(assets: assetsToDelete)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; secondaryButton: .cancel()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>}<\/p>\n\n\n\n<p>2. <strong>Explanation of UI Components<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Title<\/strong>: Displays the app name.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Delete Button<\/strong>: Initiates the deletion process.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>ProgressView<\/strong>: Shows progress during deletion.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Result Message<\/strong>: Displays success or error messages.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Permission Denied View<\/strong>: Informs the user if access is denied and provides a button to open Settings.<\/p>\n\n\n\n<p><strong>Step 6: Implement Photo Deletion Logic<\/strong><\/p>\n\n\n\n<p>Now, implement the functions to handle photo access, fetching, and deletion.<\/p>\n\n\n\n<p>1. <strong>Request Photo Library Access<\/strong>:<\/p>\n\n\n\n<p>extension ContentView {<\/p>\n\n\n\n<p>&nbsp; &nbsp; func requestPhotoLibraryAccess() {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; PHPhotoLibrary.requestAuthorization { status in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DispatchQueue.main.async {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.authorizationStatus = status<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>}<\/p>\n\n\n\n<p>2. <strong>Fetch Assets to Delete<\/strong>:<\/p>\n\n\n\n<p>extension ContentView {<\/p>\n\n\n\n<p>&nbsp; &nbsp; func fetchAssetsToDelete() {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionInProgress = true<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Fetch all image assets<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let allPhotosOptions = PHFetchOptions()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: &#8220;creationDate&#8221;, ascending: true)]<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let allPhotos = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Fetch favorited photos<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let favoritedOptions = PHFetchOptions()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; favoritedOptions.predicate = NSPredicate(format: &#8220;favorite == YES&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let favoritedPhotos = PHAsset.fetchAssets(with: .image, options: favoritedOptions)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Create a set of favorited photo IDs for quick lookup<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; var favoritedPhotoIDs = Set&lt;String&gt;()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; favoritedPhotos.enumerateObjects { (asset, _, _) in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; favoritedPhotoIDs.insert(asset.localIdentifier)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Identify non-favorited photos<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; var assetsToDeleteTemp = [PHAsset]()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; allPhotos.enumerateObjects { (asset, _, _) in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if !favoritedPhotoIDs.contains(asset.localIdentifier) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; assetsToDeleteTemp.append(asset)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Update state and show confirmation<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; DispatchQueue.main.async {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.assetsToDelete = assetsToDeleteTemp<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.deletionInProgress = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if !assetsToDeleteTemp.isEmpty {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.showConfirmation = true<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.deletionResultMessage = &#8220;No non-favorited photos found to delete.&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>}<\/p>\n\n\n\n<p>3. <strong>Perform Deletion<\/strong>:<\/p>\n\n\n\n<p>extension ContentView {<\/p>\n\n\n\n<p>&nbsp; &nbsp; func performDeletion(assets: [PHAsset]) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionInProgress = true<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; PHPhotoLibrary.shared().performChanges({<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PHAssetChangeRequest.deleteAssets(assets as NSFastEnumeration)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }) { success, error in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DispatchQueue.main.async {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; deletionInProgress = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if success {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;Successfully deleted \\(assets.count) non-favorited photos.&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;Failed to delete photos: \\(error?.localizedDescription ?? &#8220;Unknown error&#8221;)&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>}<\/p>\n\n\n\n<p>4. <strong>Open Settings Function<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Since <strong>Swift Playgrounds<\/strong> may not support opening app settings directly, this function may have limited functionality. However, you can attempt to prompt the user to open Settings.<\/p>\n\n\n\n<p>extension ContentView {<\/p>\n\n\n\n<p>&nbsp; &nbsp; func openSettings() {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; if let appSettings = URL(string: UIApplication.openSettingsURLString) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if UIApplication.shared.canOpenURL(appSettings) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UIApplication.shared.open(appSettings)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>}<\/p>\n\n\n\n<p><strong>Note<\/strong>: On <strong>Swift Playgrounds<\/strong>, some functionalities like opening Settings may be restricted. Users might need to manually navigate to Settings to grant permissions.<\/p>\n\n\n\n<p><strong>Step 7: Testing the Playground<\/strong><\/p>\n\n\n\n<p>After implementing the UI and logic, it\u00e2\u20ac\u2122s time to test your Playground to ensure it works as expected.<\/p>\n\n\n\n<p>1. <strong>Run the Playground<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 In <strong>Swift Playgrounds<\/strong>, tap the <strong>Run<\/strong> button (&#x25b6;&#xfe0f;) to execute your code.<\/p>\n\n\n\n<p>2. <strong>Grant Photo Library Access<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Upon running, the app will request access to your Photos library.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Tap \u00e2\u20ac\u0153Allow Access to All Photos\u00e2\u20ac\u009d<\/strong> when prompted.<\/p>\n\n\n\n<p>3. <strong>Initiate Deletion<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 After authorization, tap the <strong>\u00e2\u20ac\u0153Delete Non-Favorited Photos\u00e2\u20ac\u009d<\/strong> button.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 A confirmation alert will appear. <strong>Confirm<\/strong> the deletion to proceed.<\/p>\n\n\n\n<p>4. <strong>Monitor Progress and Results<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 A <strong>ProgressView<\/strong> will indicate that deletion is in progress.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Upon completion, a success or error message will display.<\/p>\n\n\n\n<p>5. <strong>Verify Deletions<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Open the <strong>Photos<\/strong> app on your iPad to ensure that only non-favorited photos have been deleted.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Remember, deleted photos move to the <strong>\u00e2\u20ac\u0153Recently Deleted\u00e2\u20ac\u009d<\/strong> album for 30 days before permanent deletion.<\/p>\n\n\n\n<p>6. <strong>Handle Permission Denial<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Test Scenario<\/strong>: Deny photo access when prompted.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Expected Behavior<\/strong>: The app should inform you that access is denied and provide an option to open Settings.<\/p>\n\n\n\n<p><strong>Tip<\/strong>: To reset photo permissions, go to <strong>Settings &gt; Privacy &gt; Photos<\/strong> on your iPad and adjust permissions for <strong>Swift Playgrounds<\/strong>.<\/p>\n\n\n\n<p><strong>Step 8: Final Considerations<\/strong><\/p>\n\n\n\n<p>While <strong>Swift Playgrounds<\/strong> is a powerful tool for learning and prototyping, developing a fully functional app with extensive UI and system integrations is more suited to <strong>Xcode<\/strong> on a Mac. However, for educational purposes and simple functionalities like the one you\u00e2\u20ac\u2122re implementing, <strong>Swift Playgrounds<\/strong> on iPad can suffice.<\/p>\n\n\n\n<p><strong>Limitations to Keep in Mind:<\/strong><\/p>\n\n\n\n<p>1. <strong>UI Capabilities<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Swift Playgrounds<\/strong> supports <strong>SwiftUI<\/strong>, but some advanced UI features available in <strong>Xcode<\/strong> might be limited.<\/p>\n\n\n\n<p>2. <strong>System Access<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Certain system functionalities and permissions might be restricted or behave differently compared to a full iOS app developed in <strong>Xcode<\/strong>.<\/p>\n\n\n\n<p>3. <strong>Deployment<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 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 <strong>Xcode<\/strong> on a Mac.<\/p>\n\n\n\n<p>4. <strong>Error Handling and Debugging<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Debugging in <strong>Swift Playgrounds<\/strong> is less sophisticated compared to <strong>Xcode<\/strong>. Be prepared for more manual testing and error checking.<\/p>\n\n\n\n<p><strong>Enhancements for Improved Functionality:<\/strong><\/p>\n\n\n\n<p>1. <strong>User Confirmation<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Implement multiple confirmation steps to prevent accidental deletions.<\/p>\n\n\n\n<p>2. <strong>Progress Feedback<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Enhance the progress indicators to provide more detailed feedback during lengthy operations.<\/p>\n\n\n\n<p>3. <strong>Selective Deletion<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Allow users to preview and select specific photos for deletion rather than deleting all non-favorited photos at once.<\/p>\n\n\n\n<p>4. <strong>Undo Functionality<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 While <strong>Photos<\/strong> handles deletions via the \u00e2\u20ac\u0153Recently Deleted\u00e2\u20ac\u009d folder, consider implementing an undo feature within your app for added user convenience.<\/p>\n\n\n\n<p>5. <strong>Backup Feature<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Before deletion, offer users the option to backup their photos to a cloud service or another storage medium.<\/p>\n\n\n\n<p><strong>Conclusion<\/strong><\/p>\n\n\n\n<p>You\u00e2\u20ac\u2122ve successfully created a <strong>Swift Playgrounds<\/strong> project on your iPad that deletes non-favorited photos from the Apple Photos library. This project demonstrates how powerful and flexible <strong>Swift Playgrounds<\/strong> can be for learning and prototyping Swift code directly on your iPad.<\/p>\n\n\n\n<p><strong>Key Takeaways:<\/strong><\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Swift Playgrounds<\/strong> is a versatile tool for coding on the go, allowing you to interact with system frameworks like <strong>Photos<\/strong>.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Always prioritize <strong>user data safety<\/strong> by implementing confirmations and providing clear feedback.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Testing<\/strong> is crucial. Ensure you thoroughly test your Playground to prevent unintended data loss.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 While <strong>Swift Playgrounds<\/strong> is excellent for learning and simple projects, transitioning to <strong>Xcode<\/strong> on a Mac is recommended for more complex app development and deployment.<\/p>\n\n\n\n<p><strong>Next Steps:<\/strong><\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Explore More SwiftUI Features<\/strong>: Enhance your app\u00e2\u20ac\u2122s UI by exploring more <strong>SwiftUI<\/strong> components and modifiers.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Learn About App Deployment<\/strong>: If you wish to develop full-fledged iOS apps, consider learning <strong>Xcode<\/strong> and the app deployment process.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Implement Additional Features<\/strong>: Expand your Playground by adding features like photo previews, selective deletion, or integration with other services.<\/p>\n\n\n\n<p>Happy coding, and always remember to handle user data responsibly!<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a6<\/p>\n\n\n\n<p>Certainly! While I can\u00e2\u20ac\u2122t provide actual images, I can offer a <strong>detailed step-by-step guide<\/strong> with <strong>code snippets<\/strong> and <strong>descriptions<\/strong> of what your app will look like in the <strong>Swift Playgrounds<\/strong> app on your iPad. This will help you visualize the interface and understand how each component functions.<\/p>\n\n\n\n<p><strong>Overview<\/strong><\/p>\n\n\n\n<p>You will create a simple SwiftUI-based interface within Swift Playgrounds that:<\/p>\n\n\n\n<p>1. <strong>Displays a Title<\/strong>: \u00e2\u20ac\u0153Photo Cleaner\u00e2\u20ac\u009d.<\/p>\n\n\n\n<p>2. <strong>Shows a Button<\/strong>: \u00e2\u20ac\u0153Delete Non-Favorited Photos\u00e2\u20ac\u009d.<\/p>\n\n\n\n<p>3. <strong>Includes a Progress Indicator<\/strong>: Visible during the deletion process.<\/p>\n\n\n\n<p>4. <strong>Displays Status Messages<\/strong>: Success or error messages post-deletion.<\/p>\n\n\n\n<p>5. <strong>Handles Permissions<\/strong>: Requests access to the Photos library and guides the user if access is denied.<\/p>\n\n\n\n<p><strong>Step-by-Step Instructions<\/strong><\/p>\n\n\n\n<p><strong>Step 1: Open Swift Playgrounds and Create a New Playground<\/strong><\/p>\n\n\n\n<p>1. <strong>Launch Swift Playgrounds<\/strong> on your iPad.<\/p>\n\n\n\n<p>2. <strong>Create a New Playground<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Tap the <strong>\u00e2\u20ac\u009d+\u00e2\u20ac\u009d<\/strong> icon or <strong>\u00e2\u20ac\u0153Create Playground\u00e2\u20ac\u009d<\/strong>.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Choose <strong>\u00e2\u20ac\u0153Blank\u00e2\u20ac\u009d<\/strong> to start from scratch.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Name your Playground (e.g., PhotoCleaner) and tap <strong>\u00e2\u20ac\u0153Create\u00e2\u20ac\u009d<\/strong>.<\/p>\n\n\n\n<p><strong>Step 2: Set Up the Playground Environment<\/strong><\/p>\n\n\n\n<p>Ensure you have the necessary frameworks imported and set up the live view to display your SwiftUI interface.<\/p>\n\n\n\n<p>1. <strong>Import Frameworks<\/strong>:<\/p>\n\n\n\n<p>At the top of your Playground, add the following imports:<\/p>\n\n\n\n<p>import SwiftUI<\/p>\n\n\n\n<p>import Photos<\/p>\n\n\n\n<p>import PlaygroundSupport<\/p>\n\n\n\n<p>2. <strong>Set the Live View<\/strong>:<\/p>\n\n\n\n<p>At the bottom of your Playground, set the live view to your main ContentView:<\/p>\n\n\n\n<p>PlaygroundPage.current.setLiveView(ContentView())<\/p>\n\n\n\n<p><strong>Step 3: Design the User Interface (UI)<\/strong><\/p>\n\n\n\n<p>Create a SwiftUI ContentView that includes all necessary UI components.<\/p>\n\n\n\n<p>1. <strong>Define <\/strong><strong>ContentView<\/strong><strong> Structure<\/strong>:<\/p>\n\n\n\n<p>struct ContentView: View {<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var authorizationStatus: PHAuthorizationStatus = .notDetermined<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var deletionInProgress = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var deletionResultMessage = &#8220;&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var showConfirmation = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; @State private var assetsToDelete: [PHAsset] = []<\/p>\n\n\n\n<p>&nbsp; &nbsp; var body: some View {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; NavigationView {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; VStack(spacing: 20) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Photo Cleaner&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .font(.largeTitle)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .fontWeight(.bold)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if authorizationStatus == .authorized {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Button(action: {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fetchAssetsToDelete()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(deletionInProgress ? &#8220;Preparing to Delete&#8230;&#8221; : &#8220;Delete Non-Favorited Photos&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .foregroundColor(.white)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .frame(maxWidth: .infinity)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .background(Color.red)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .cornerRadius(8)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .disabled(deletionInProgress)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if deletionInProgress {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ProgressView(&#8220;Deleting photos&#8230;&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .progressViewStyle(CircularProgressViewStyle())<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if !deletionResultMessage.isEmpty {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(deletionResultMessage)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .foregroundColor(.green)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if authorizationStatus == .denied || authorizationStatus == .restricted {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; VStack {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Access to Photos is denied.&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .font(.headline)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .multilineTextAlignment(.center)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding(.bottom, 10)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Please enable access in Settings to allow the app to delete non-favorited photos.&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .multilineTextAlignment(.center)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding(.horizontal)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Button(action: {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; openSettings()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Open Settings&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .foregroundColor(.blue)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .background(Color(UIColor.systemGray5))<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .cornerRadius(8)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Text(&#8220;Requesting access to Photos&#8230;&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .onAppear {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; requestPhotoLibraryAccess()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .padding()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .navigationBarTitle(&#8220;Photo Cleaner&#8221;, displayMode: .inline)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .alert(isPresented: $showConfirmation) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Alert(<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title: Text(&#8220;Confirm Deletion&#8221;),<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message: Text(&#8220;Are you sure you want to delete \\(assetsToDelete.count) non-favorited photos? This action cannot be undone.&#8221;),<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; primaryButton: .destructive(Text(&#8220;Delete&#8221;)) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; performDeletion(assets: assetsToDelete)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; secondaryButton: .cancel()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>}<\/p>\n\n\n\n<p>2. <strong>Description of UI Components<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>NavigationView<\/strong>: Provides a navigation bar with the title \u00e2\u20ac\u0153Photo Cleaner\u00e2\u20ac\u009d.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>VStack<\/strong>: Arranges elements vertically with spacing.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Title Text<\/strong>: Displays \u00e2\u20ac\u0153Photo Cleaner\u00e2\u20ac\u009d prominently.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Delete Button<\/strong>: Initiates the deletion process; turns into \u00e2\u20ac\u0153Preparing to Delete\u00e2\u20ac\u00a6\u00e2\u20ac\u009d during processing.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>ProgressView<\/strong>: Shows a spinner with the message \u00e2\u20ac\u0153Deleting photos\u00e2\u20ac\u00a6\u00e2\u20ac\u009d when deletion is in progress.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Result Message<\/strong>: Displays success or error messages after the deletion process.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Permission Denied View<\/strong>: Informs the user if access is denied and provides a button to open Settings.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Permission Request Text<\/strong>: Shows \u00e2\u20ac\u0153Requesting access to Photos\u00e2\u20ac\u00a6\u00e2\u20ac\u009d while awaiting user permission.<\/p>\n\n\n\n<p><strong>Step 4: Implement Photo Deletion Logic<\/strong><\/p>\n\n\n\n<p>Add functions to handle photo library access, fetching non-favorited photos, and performing deletions.<\/p>\n\n\n\n<p>1. <strong>Extend <\/strong><strong>ContentView<\/strong><strong> with Functions<\/strong>:<\/p>\n\n\n\n<p>extension ContentView {<\/p>\n\n\n\n<p>&nbsp; &nbsp; \/\/ Request Photo Library Access<\/p>\n\n\n\n<p>&nbsp; &nbsp; func requestPhotoLibraryAccess() {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; PHPhotoLibrary.requestAuthorization { status in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DispatchQueue.main.async {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.authorizationStatus = status<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; \/\/ Fetch Assets to Delete<\/p>\n\n\n\n<p>&nbsp; &nbsp; func fetchAssetsToDelete() {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionInProgress = true<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Fetch all image assets<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let allPhotosOptions = PHFetchOptions()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: &#8220;creationDate&#8221;, ascending: true)]<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let allPhotos = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Fetch favorited photos<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let favoritedOptions = PHFetchOptions()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; favoritedOptions.predicate = NSPredicate(format: &#8220;favorite == YES&#8221;)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; let favoritedPhotos = PHAsset.fetchAssets(with: .image, options: favoritedOptions)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Create a set of favorited photo IDs for quick lookup<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; var favoritedPhotoIDs = Set&lt;String&gt;()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; favoritedPhotos.enumerateObjects { (asset, _, _) in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; favoritedPhotoIDs.insert(asset.localIdentifier)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Identify non-favorited photos<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; var assetsToDeleteTemp = [PHAsset]()<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; allPhotos.enumerateObjects { (asset, _, _) in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if !favoritedPhotoIDs.contains(asset.localIdentifier) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; assetsToDeleteTemp.append(asset)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Update state and show confirmation<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; DispatchQueue.main.async {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.assetsToDelete = assetsToDeleteTemp<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.deletionInProgress = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if !assetsToDeleteTemp.isEmpty {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.showConfirmation = true<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.deletionResultMessage = &#8220;No non-favorited photos found to delete.&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; \/\/ Perform Deletion<\/p>\n\n\n\n<p>&nbsp; &nbsp; func performDeletion(assets: [PHAsset]) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionInProgress = true<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; PHPhotoLibrary.shared().performChanges({<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PHAssetChangeRequest.deleteAssets(assets as NSFastEnumeration)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }) { success, error in<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DispatchQueue.main.async {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; deletionInProgress = false<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if success {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;Successfully deleted \\(assets.count) non-favorited photos.&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; deletionResultMessage = &#8220;Failed to delete photos: \\(error?.localizedDescription ?? &#8220;Unknown error&#8221;)&#8221;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; \/\/ Open Settings Function<\/p>\n\n\n\n<p>&nbsp; &nbsp; func openSettings() {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; if let appSettings = URL(string: UIApplication.openSettingsURLString) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if UIApplication.shared.canOpenURL(appSettings) {<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UIApplication.shared.open(appSettings)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; }<\/p>\n\n\n\n<p>&nbsp; &nbsp; }<\/p>\n\n\n\n<p>}<\/p>\n\n\n\n<p>2. <strong>Explanation of Functions<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>requestPhotoLibraryAccess()<\/strong>: Requests permission to access the Photos library. Updates the authorizationStatus based on user response.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>fetchAssetsToDelete()<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Fetches all image assets.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Fetches only favorited photos.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Identifies non-favorited photos by comparing all photos with favorited photos.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Updates the state to show a confirmation alert if there are photos to delete.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>performDeletion(assets:)<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Deletes the specified assets using PHAssetChangeRequest.deleteAssets.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Updates the deletionResultMessage based on the success or failure of the operation.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>openSettings()<\/strong>: Attempts to open the app\u00e2\u20ac\u2122s settings in the Settings app to allow the user to grant photo access permissions manually.<\/p>\n\n\n\n<p><strong>Step 5: Set Up Live View<\/strong><\/p>\n\n\n\n<p>Ensure that your ContentView is set as the live view so that you can interact with the UI.<\/p>\n\n\n\n<p>1. <strong>Set Live View<\/strong>:<\/p>\n\n\n\n<p>At the bottom of your Playground, ensure the following line exists:<\/p>\n\n\n\n<p>PlaygroundPage.current.setLiveView(ContentView())<\/p>\n\n\n\n<p>This line tells Swift Playgrounds to display ContentView as the live view when you run the Playground.<\/p>\n\n\n\n<p><strong>Step 6: Run and Test the Playground<\/strong><\/p>\n\n\n\n<p>1. <strong>Run the Playground<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Tap the <strong>Run<\/strong> button (&#x25b6;&#xfe0f;) at the top of the Playground editor.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 The live view should appear, displaying your designed UI.<\/p>\n\n\n\n<p>2. <strong>Grant Photo Library Access<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Upon first run, a prompt will appear requesting access to your Photos library.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Tap<\/strong>: <strong>\u00e2\u20ac\u0153Allow Access to All Photos\u00e2\u20ac\u009d<\/strong>.<\/p>\n\n\n\n<p>3. <strong>Interact with the App<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Delete Button<\/strong>: Tap the \u00e2\u20ac\u0153Delete Non-Favorited Photos\u00e2\u20ac\u009d button.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Confirmation Alert<\/strong>: An alert will appear asking for confirmation to delete the identified photos.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Tap<\/strong>: <strong>\u00e2\u20ac\u0153Delete\u00e2\u20ac\u009d<\/strong> to proceed or <strong>\u00e2\u20ac\u0153Cancel\u00e2\u20ac\u009d<\/strong> to abort.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Progress Indicator<\/strong>: While deletion is in progress, a spinner with the message \u00e2\u20ac\u0153Deleting photos\u00e2\u20ac\u00a6\u00e2\u20ac\u009d will appear.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Result Message<\/strong>: After completion, a message will display indicating success or failure.<\/p>\n\n\n\n<p>4. <strong>Verify Deletions<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Open the <strong>Photos<\/strong> app on your iPad.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Navigate to <strong>\u00e2\u20ac\u0153Recently Deleted\u00e2\u20ac\u009d<\/strong> to see the deleted photos (they remain here for 30 days before permanent deletion).<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Ensure that only non-favorited photos were deleted.<\/p>\n\n\n\n<p><strong>Step 7: Handle Permission Denial<\/strong><\/p>\n\n\n\n<p>1. <strong>Test Denied Permissions<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Reset Permissions<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Go to <strong>Settings &gt; Privacy &gt; Photos<\/strong> on your iPad.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 Find <strong>Swift Playgrounds<\/strong> and set access to <strong>\u00e2\u20ac\u0153Never\u00e2\u20ac\u009d<\/strong>.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Run the Playground<\/strong> again.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Expected Behavior<\/strong>: The app should display a message informing you that access is denied and provide a button to open Settings.<\/p>\n\n\n\n<p>2. <strong>Attempt to Open Settings<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Tap<\/strong>: <strong>\u00e2\u20ac\u0153Open Settings\u00e2\u20ac\u009d<\/strong> button.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Note<\/strong>: Due to Swift Playgrounds limitations, this action might not open Settings directly. If it doesn\u00e2\u20ac\u2122t, manually navigate to <strong>Settings &gt; Privacy &gt; Photos<\/strong> and enable access for <strong>Swift Playgrounds<\/strong>.<\/p>\n\n\n\n<p><strong>Step 8: Final Testing and Safety Measures<\/strong><\/p>\n\n\n\n<p>1. <strong>Backup Your Photos<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Highly Recommended<\/strong>: Before performing deletions, <strong>backup<\/strong> your photos to iCloud, a computer, or another storage solution to prevent accidental loss.<\/p>\n\n\n\n<p>2. <strong>Test with Sample Photos<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Create a Test Album<\/strong>: Add a few non-favorited photos to a separate album for safe testing.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Run Deletion<\/strong>: Execute the Playground to delete these test photos.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Verify<\/strong>: Ensure that only the intended photos are deleted.<\/p>\n\n\n\n<p>3. <strong>Handle Errors Gracefully<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Simulate Errors<\/strong>: Disconnect from the internet or revoke permissions mid-operation to test error handling.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Observe Messages<\/strong>: Ensure that error messages are displayed appropriately.<\/p>\n\n\n\n<p><strong>Summary of the App\u00e2\u20ac\u2122s Interface<\/strong><\/p>\n\n\n\n<p>Here\u00e2\u20ac\u2122s a <strong>textual visualization<\/strong> of what your app\u00e2\u20ac\u2122s interface will look like in <strong>Swift Playgrounds<\/strong>:<\/p>\n\n\n\n<p>1. <strong>Title<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Text<\/strong>: \u00e2\u20ac\u0153Photo Cleaner\u00e2\u20ac\u009d<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Font<\/strong>: Large, bold, centered at the top.<\/p>\n\n\n\n<p>2. <strong>Delete Button<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Label<\/strong>: \u00e2\u20ac\u0153Delete Non-Favorited Photos\u00e2\u20ac\u009d<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Appearance<\/strong>: Red background, white text, rounded corners.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Behavior<\/strong>: Disabled and shows \u00e2\u20ac\u0153Preparing to Delete\u00e2\u20ac\u00a6\u00e2\u20ac\u009d when an operation is in progress.<\/p>\n\n\n\n<p>3. <strong>Progress Indicator<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Visible<\/strong>: Only during the deletion process.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Appearance<\/strong>: Circular spinner with the label \u00e2\u20ac\u0153Deleting photos\u00e2\u20ac\u00a6\u00e2\u20ac\u009d.<\/p>\n\n\n\n<p>4. <strong>Status Messages<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Success<\/strong>: \u00e2\u20ac\u0153Successfully deleted X non-favorited photos.\u00e2\u20ac\u009d<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Failure<\/strong>: \u00e2\u20ac\u0153Failed to delete photos: [Error Description]\u00e2\u20ac\u009d<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>No Photos to Delete<\/strong>: \u00e2\u20ac\u0153No non-favorited photos found to delete.\u00e2\u20ac\u009d<\/p>\n\n\n\n<p>5. <strong>Permission Denied View<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Text<\/strong>: Informative message about denied access.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Button<\/strong>: \u00e2\u20ac\u0153Open Settings\u00e2\u20ac\u009d to guide the user to enable permissions.<\/p>\n\n\n\n<p>6. <strong>Confirmation Alert<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Title<\/strong>: \u00e2\u20ac\u0153Confirm Deletion\u00e2\u20ac\u009d<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Message<\/strong>: \u00e2\u20ac\u0153Are you sure you want to delete X non-favorited photos? This action cannot be undone.\u00e2\u20ac\u009d<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Buttons<\/strong>:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Delete<\/strong>: Initiates deletion.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Cancel<\/strong>: Aborts the operation.<\/p>\n\n\n\n<p><strong>Best Practices and Final Tips<\/strong><\/p>\n\n\n\n<p>1. <strong>Always Backup Photos<\/strong>: Before running deletion operations, ensure your important photos are backed up to prevent data loss.<\/p>\n\n\n\n<p>2. <strong>User Confirmation<\/strong>: Never delete photos without explicit user confirmation to maintain trust and prevent accidental deletions.<\/p>\n\n\n\n<p>3. <strong>Handle Errors Gracefully<\/strong>: Always account for potential errors and provide meaningful feedback to the user.<\/p>\n\n\n\n<p>4. <strong>Test Extensively<\/strong>: Use test photos to verify that your app behaves as expected without risking important data.<\/p>\n\n\n\n<p>5. <strong>Understand Limitations<\/strong>: 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.<\/p>\n\n\n\n<p>6. <strong>Enhance Accessibility<\/strong>: Ensure your app is accessible to all users by supporting VoiceOver, dynamic type, and sufficient color contrast.<\/p>\n\n\n\n<p><strong>Conclusion<\/strong><\/p>\n\n\n\n<p>You\u00e2\u20ac\u2122ve successfully created a <strong>SwiftUI-based app<\/strong> within <strong>Swift Playgrounds<\/strong> on your iPad that:<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Requests Access<\/strong>: Handles permissions to access the Photos library.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Identifies Non-Favorited Photos<\/strong>: Fetches and filters photos not marked as favorites.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Deletes Photos<\/strong>: Removes the identified photos upon user confirmation.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Provides User Feedback<\/strong>: Displays progress indicators and status messages.<\/p>\n\n\n\n<p>\u00e2\u20ac\u00a2 <strong>Handles Permission Denial<\/strong>: Guides users to enable permissions if initially denied.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p><strong>Happy Coding!<\/strong> If you have any further questions or need additional assistance, feel free to ask.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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\u00e2\u20ac\u2122s important to note that Swift Playgrounds [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[17],"tags":[],"class_list":["post-643437","post","type-post","status-publish","format-standard","hentry","category-posts"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts\/643437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/comments?post=643437"}],"version-history":[{"count":3,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts\/643437\/revisions"}],"predecessor-version":[{"id":643441,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts\/643437\/revisions\/643441"}],"wp:attachment":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/media?parent=643437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/categories?post=643437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/tags?post=643437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}