{"id":30699864,"url":"https://github.com/cewitte/hotprospects","last_synced_at":"2025-09-02T11:42:29.205Z","repository":{"id":299877509,"uuid":"1003603457","full_name":"cewitte/HotProspects","owner":"cewitte","description":"Paul Hudson's 100 Days of SwiftUI Project 16: HotProspects.","archived":false,"fork":false,"pushed_at":"2025-07-17T21:38:11.000Z","size":4073,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-18T01:55:04.213Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cewitte.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-17T11:51:28.000Z","updated_at":"2025-07-17T21:38:16.000Z","dependencies_parsed_at":"2025-06-18T19:41:28.270Z","dependency_job_id":"0f942555-cc3d-4243-9d90-03acfd089c68","html_url":"https://github.com/cewitte/HotProspects","commit_stats":null,"previous_names":["cewitte/hotprospects"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/cewitte/HotProspects","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FHotProspects","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FHotProspects/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FHotProspects/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FHotProspects/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cewitte","download_url":"https://codeload.github.com/cewitte/HotProspects/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FHotProspects/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273279728,"owners_count":25077318,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-02T11:42:27.194Z","updated_at":"2025-09-02T11:42:29.196Z","avatar_url":"https://github.com/cewitte.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hot Prospects: Introduction\n\n## Paul Hudson's ([@twostraws](https://x.com/twostraws)) 100 Days of Swift UI Project 16\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/hot-prospects-introduction)\n\n\u003eIn this project we’re going to build Hot Prospects, which is an app to track who you meet at conferences. You’ve probably seen apps like it before: it will show a QR code that stores your attendee information, then others can scan that code to add you to their list of possible leads for later follow up.\n\n### Letting users select items in a List\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/letting-users-select-items-in-a-list)\n\nReally interesting technique for letting users select multiple items in a list and present it in a readable way (see animation below):\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/multiple_selection.gif\" width=\"300\"/\u003e\n\u003c/div\u003e\n\nThis interesting effect is achieved by adding the `EditButton()` and the `selection.formatted()` in the code below:\n\n```swift\nimport SwiftUI\n\nstruct ContentView: View {\n    let users = [\"Tohru\", \"Yuki\", \"Kyo\", \"Momiji\"]\n    @State private var selection = Set\u003cString\u003e()\n    \n    var body: some View {\n        List(users, id:\\.self, selection: $selection) { user in\n            Text(user)\n        }\n        \n        if !selection.isEmpty {\n            Text(\"You selected: \\(selection.formatted())\")\n        }\n        \n        EditButton()\n    }\n}\n```\n\n### Understanding Swift’s Result type\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/understanding-swifts-result-type)\n\n\u003e Swift provides a special type called Result that allows us to encapsulate either a successful value or some kind of error type, all in a single piece of data. So, in the same way that an optional might hold a string or might hold nothing at all, for example, Result might hold a string or might hold an error. The syntax for using it is a little odd at first, but it does have an important part to play in our projects.\n\n`Result`is really interesting, although its syntax is a bit odd. Here's Paul's example usage:\n\n```swift\nfunc fetchReadings() async {\n    let fetchTask = Task {\n        let url = URL(string: \"https://hws.dev/readings.json\")!\n        let (data, _) = try await URLSession.shared.data(from: url)\n        let readings = try JSONDecoder().decode([Double].self, from: data)\n        return \"Found \\(readings.count) readings\"\n    }\n}\n```\n\nPaul presents two options for handling `Result`, my preferred way...\n\n```swift\ndo {\n    output = try result.get()\n} catch {\n    output = \"Error: \\(error.localizedDescription)\"\n}\n```\n\n... and through the `switch` statement as below:\n\n```swift\nswitch result {\n    case .success(let str):\n        output = str\n    case .failure(let error):\n        output = \"Error: \\(error.localizedDescription)\"\n}\n```\n\n### Controlling image interpolation in SwiftUI\n\nBasically, `interpolation(.none)` in the code below ensures that the small image will be pixelated, but not blurred, when resized.\n\n```swift\nImage(.example)\n    .interpolation(.none)\n    .resizable()\n    .scaledToFit()\n    .background(.black)\n```\n\nThe result:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/interpolation_none.png\" width=\"300\"/\u003e\n\u003c/div\u003e\n\n### Creating context menus\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/creating-context-menus)\n\n\u003eSwiftUI lets us attach context menus to objects to provide this extra functionality, all done using the `contextMenu()` modifier. You can pass this a selection of buttons and they’ll be shown in order, so we could build a simple context menu to control a view’s background color like this:\n\n```swift\nText(\"Hello, color!\")\n    .padding()\n    .background(backgroundColor)\n\nText(\"Change Color\")\n    .padding()\n    .contextMenu {\n        Button(\"Red\", systemImage: \"checkmark.circle.fill\", role: .destructive) {\n            backgroundColor = .red\n        }\n        \n        Button(\"Green\") {\n            backgroundColor = .green\n        }\n        \n        Button(\"Blue\") {\n            backgroundColor = .blue\n        }\n    }\n```\n\n### Adding custom row swipe actions to a List\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/adding-custom-row-swipe-actions-to-a-list)\n\n\u003eWe get this full functionality in SwiftUI using the `swipeActions()1 modifier, which lets us register one or more buttons on one or both sides of a list row. By default buttons will be placed on the right edge of the row, and won’t have any color, so this will show a single gray button when you swipe from right to left.\n\n```swift\nList(users, id:\\.self, selection: $selection) { user in\n    Text(user)\n        .swipeActions {\n            Button(\"Delete\", systemImage: \"minus.circle\", role: .destructive) {\n                print(\"Deleting\")\n            }\n        }\n        .swipeActions(edge: .leading) {\n            Button(\"Pin\", systemImage: \"pin\") {\n                print(\"Pinning\")\n            }\n            .tint(.orange)\n        }\n}\n```\n\n### Scheduling local notifications\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/scheduling-local-notifications)\n\n\u003eiOS has a framework called UserNotifications that does pretty much exactly what you expect: lets us create notifications to the user that can be shown on the lock screen. We have two types of notifications to work with, and they differ depending on where they were created: local notifications are ones we schedule locally, and remote notifications (commonly called push notifications) are sent from a server somewhere.\n\n\u003eRemote notifications require a server to work, because you send your message to Apple’s push notification service (APNS), which then forwards it to users. But local notifications are nice and easy in comparison, because we can send any message at any time as long as the user allows it.\n\nHere's the code:\n\n```swift\nVStack {\n    Button(\"Request Permission\") {\n        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in\n            if success {\n                print(\"All set!\")\n            } else if let error {\n                print(error.localizedDescription)\n            }\n        }\n    }\n\n    Button(\"Schedule Notification\") {\n        let content = UNMutableNotificationContent()\n        content.title = \"Feed the cat\"\n        content.subtitle = \"It looks hungry\"\n        content.sound = UNNotificationSound.default\n\n        // show this notification five seconds from now\n        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)\n\n        // choose a random identifier\n        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)\n\n        // add our notification request\n        UNUserNotificationCenter.current().add(request)\n    }\n}\n```\n\n### Adding Swift package dependencies in Xcode\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/adding-swift-package-dependencies-in-xcode)\n\n\u003eXcode comes with a dependency manager built in, called Swift Package Manager (SPM). You can tell Xcode the URL of some code that’s stored online, and it will download it for you. You can even tell it what version to download, which means if the remote code changes sometime in the future you can be sure it won’t break your existing code.\n\n\u003eThe first step is to add the package to our project: go to the File menu and choose Add Package Dependencies. Enter the URL where the code for my example package is stored. Xcode will fetch the package, read its configuration, and show you options asking which version you want to use. The default will be “Version – Up to Next Major”, which is the most common one to use and means if the author of the package updates it in the future then as long as they don’t introduce breaking changes Xcode will update the package to use the new versions.\n\n\u003eThe reason this is possible is because most developers have agreed a system of semantic versioning (SemVer) for their code. If you look at a version like 1.5.3, then the 1 is considered the major number, the 5 is considered the minor number, and the 3 is considered the patch number. If developers follow SemVer correctly, then they should:\n\n- \u003e Change the patch number when fixing a bug as long as it doesn’t break any APIs or add features.\n- \u003e Change the minor number when they added features that don’t break any APIs.\n- \u003e Change the major number when they do break APIs.\n\nAn interesting part of the code:\n\n\u003eWe need to convert that array of integers into strings. This only takes one line of code in Swift, because sequences have a `map()` method that lets us convert an array of one type into an array of another type by applying a function to each element. In our case, we want to initialize a new string from each integer, so we can use `String.init` as the function we want to call.\n\n```swift\nvar results: String {\n    let selected = possibleNumbers.random(7).sorted()\n    let strings = selected.map(String.init)\n    return strings.formatted()\n}\n```\n\n### Building our tab bar\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/building-our-tab-bar)\n\nBranch: `release`\n\n`TavView`is very easy to be implemented, and the code speaks for itself:\n\n```swift\nTabView {\n    ProspectsView()\n        .tabItem {\n            Label(\"Everyone\", systemImage: \"person.3\")\n        }\n    ProspectsView()\n        .tabItem {\n            Label(\"Contacted\", systemImage: \"checkmark.circle\")\n        }\n    ProspectsView()\n        .tabItem {\n            Label(\"Uncontacted\", systemImage: \"questionmark.diamond\")\n        }\n    MeView()\n        .tabItem {\n            Label(\"Me\", systemImage: \"person.crop.square\")\n        }\n}\n```\n\n### Storing our data with SwiftData\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/storing-our-data-with-swiftdata)\n\nNotes on using `SwiftData`:\n\nIt requires a class that works as a model, like the one below which includes the `@Model` macro _(note: don't forget to `import SwiftData` whenever `model`, etc are referenced)_:\n\n```swift\n@Model\nclass Prospect {\n    var name: String\n    var emailAddress: String\n    var isContacted: Bool\n}\n```\n\nThen add the `.modelContainer()`to hold the data as below:\n\n```swift\nWindowGroup {\n    ContentView()\n}\n.modelContainer(for: Prospect.self)\n```\n\nFinally, use it within your views...\n\n```swift\n@Query(sort: \\Prospect.name) var prospects: [Prospect]\n@Environment(\\.modelContext) var modelContext\n```\n\n... while not forgetting to add the `.modelContainer` for previews:\n\n```swift\n#Preview {\n    ProspectsView(filter: .none)\n        .modelContainer(for: Prospect.self)\n}\n```\n\n### Dynamically filtering our SwiftData query\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/dynamically-filtering-our-swiftdata-query)\n\nBranch: `release`\n\nThe line below queries the database for all the Prospects and assings them to a Prospect array sorted by name.\n\n`@Query(sort: \\Prospect.name) var prospects: [Prospect]`\n\n\u003eWe already have a default query in place, but if we add an initializer we can override that when a filter is set.\n\n```swift\ninit(filter: FilterType) {\n    self.filter = filter\n\n    if filter != .none {\n        let showContactedOnly = filter == .contacted\n\n        _prospects = Query(filter: #Predicate {\n            $0.isContacted == showContactedOnly\n        }, sort: [SortDescriptor(\\Prospect.name)])\n    }\n}\n```\n\n### Generating and scaling up a QR code\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/generating-and-scaling-up-a-qr-code)\n\nHere Paul teaches us how to create a QRCode in Swift, and perhaps unsurprisingly, Apple has a library to help us do so. The first task is importing this package:\n\n```swift\nimport CoreImage.CIFilterBuiltins\n```\n\nThen here's a function that easily creates (but, of course, not reads) QRCodes:\n\n```swift\nfunc generateQRCode(from string: String) -\u003e UIImage {\n    filter.message = Data(string.utf8)\n    \n    if let outPutImage = filter.outputImage {\n        if let cgimage = context.createCGImage(outPutImage, from: outPutImage.extent) {\n            \n            return UIImage(cgImage: cgimage)\n        }\n    }\n    \n    return UIImage(systemName: \"xmark.circle\") ?? UIImage()\n}\n```\n\nSwiftUI's image generation process is kind of confusing, and it's arguably responsible for the convoluted code above.\n\nAnother important aspect when generating QRCodes is that SwiftUI will generate images only big enough to show the necessary pixels on screen, which will likely be too small. If you scale the image, it will also try to interpolate, giving your QRCode an undesired blur effect. That's why we need to turn interpolation off as below:\n\n```swift\n Image(uiImage: generateQRCode(from: \"\\(name)\\n\\(emailAddress)\"))\n    .interpolation(.none)\n    .resizable()\n    .scaledToFit()\n    .frame(width: 200, height: 200)\n```\n\nI also find surprising how there's no need to specify the QRCode type. iOS seems to understand the name and email in the example above in two consecutive lines:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/scanned_qrcode.jpeg\" width=\"300\"/\u003e\n\u003c/div\u003e\n\nInteresting enough is that I can also use Swift libraries to customize QRCodes. With a little help from ChatGPT, the code below creates a QRCode with custom colors and a logo in the middle (here only for my reference, I haven't tested the result yet):\n\n```swift\nimport SwiftUI\nimport CoreImage.CIFilterBuiltins\n\nstruct CustomQRCodeView: View {\n    let context = CIContext()\n    let filter = CIFilter.qrCodeGenerator()\n    \n    var body: some View {\n        if let qrImage = generateCustomQRCode(from: \"https://www.ag24horas.com.br\") {\n            Image(uiImage: qrImage)\n                .resizable()\n                .interpolation(.none)\n                .scaledToFit()\n                .frame(width: 250, height: 250)\n        } else {\n            Text(\"Error while creating the QRCode\")\n        }\n    }\n    \n    func generateCustomQRCode(from string: String) -\u003e UIImage? {\n        // 1. Creates the QRCode\n        let data = Data(string.utf8)\n        filter.setValue(data, forKey: \"inputMessage\")\n\n        // the optional value below increases the error correction level to \"H\" (High)\n        filter.setValue(\"H\", forKey: \"inputCorrectionLevel\")\n        \n        guard let qrCIImage = filter.outputImage else { return nil }\n\n        // 2. Applies the color\n        let colorFilter = CIFilter.falseColor()\n        colorFilter.inputImage = qrCIImage\n        colorFilter.color0 = CIColor(color: UIColor.systemPurple) // cor dos quadrados\n        colorFilter.color1 = CIColor(color: UIColor.white)        // cor de fundo\n        \n        guard let coloredQRImage = colorFilter.outputImage else { return nil }\n\n        // 3. Converts to CGImage e resizes\n        if let cgImage = context.createCGImage(coloredQRImage, from: coloredQRImage.extent) {\n            let qrUIImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .up)\n            \n            // 4. Add logo to the middle of the image\n            return addLogo(to: qrUIImage, logo: UIImage(named: \"logo\")!)\n        }\n        \n        return nil\n    }\n    \n    func addLogo(to qrImage: UIImage, logo: UIImage) -\u003e UIImage {\n        let size = qrImage.size\n        let renderer = UIGraphicsImageRenderer(size: size)\n        \n        return renderer.image { _ in\n            qrImage.draw(in: CGRect(origin: .zero, size: size))\n            \n            let logoSize = CGSize(width: size.width * 0.25, height: size.height * 0.25)\n            let logoOrigin = CGPoint(\n                x: (size.width - logoSize.width) / 2,\n                y: (size.height - logoSize.height) / 2\n            )\n            \n            logo.draw(in: CGRect(origin: logoOrigin, size: logoSize))\n        }\n    }\n}\n```\n\n### Posting notifications to the lock screen\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/posting-notifications-to-the-lock-screen)\n\nBranch: `release`\n\nSending a local notification (one that does not relies on Apple's cloud services) requires 2 steps:\n\n1- Import the package\n\n```swift\nimport UserNotifications\n```\n\n2- Create a `func` to add the notification:\n\n```swift\nfunc addNotification(for prospect: Prospect) {\n    let center = UNUserNotificationCenter.current()\n\n    let addRequest = {\n        let content = UNMutableNotificationContent()\n        content.title = \"Contact \\(prospect.name)\"\n        content.subtitle = prospect.emailAddress\n        content.sound = UNNotificationSound.default\n\n        var dateComponents = DateComponents()\n        dateComponents.hour = 9\n        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)\n\n        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)\n        center.add(request)\n    }\n\n    center.getNotificationSettings { settings in\n        if settings.authorizationStatus == .authorized {\n            addRequest()\n        } else {\n            center.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in\n                if success {\n                    addRequest()\n                } else if let error {\n                    print(error.localizedDescription)\n                }\n            }\n        }\n    }\n}\n```\n\nThe code above will trigger an alert at 9am. For testing purposes, we can replace the `trigger` above with the following code, which triggers the alert after 5 seconds:\n\n```swift\nlet trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)\n```\n\nHere's the final result (with the alert set to 5 seconds for testing):\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/trigger_alert.gif\" width=\"300\"/\u003e\n\u003c/div\u003e\n\n### Challenge 1\n\n\u003eAdd an icon to the “Everyone” screen showing whether a prospect was contacted or not.\n\nI was able to complete this challenge with a single line of code:\n\n```swift\n+ (title == \"Everyone\" \u0026\u0026 prospect.isContacted ? Text(\" \") + Text(Image(systemName: \"checkmark.seal.fill\")) : Text(\"\"))\n```\n\nThere are certainly more elegant solutions, but being able to resolve this with a single line in a couple of minutes was enough for me. Here's the end result:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/challenge_1.gif\" width=\"300\"/\u003e\n\u003c/div\u003e\n\n### Challenge 2\n\n\u003eAdd an editing screen, so users can adjust the name and email address of someone they scanned previously. (Tip: Use the simple form of `NavigationLink` rather than `navigationDestination()`, to avoid your list selection code confusing the navigation link.)\n\nTo complete this challenge, I created a simple `EditProspectView` as below:\n\n```swift\nimport SwiftUI\n\nstruct EditProspectView: View {   \n    @State var prospect: Prospect\n    \n    var body: some View {\n        NavigationStack {\n            Form {\n                Section(footer: Text(\"Changes are saved automatically.\")) {\n                    TextField(\"Name\", text: $prospect.name)\n                    TextField(\"Email\", text: $prospect.emailAddress)\n                }\n            }\n            .navigationTitle(\"Edit \" + prospect.name)\n        }\n    }\n}\n\n#Preview {\n    EditProspectView(prospect: Prospect(name: \"Test\", emailAddress: \"test@test.com\"))\n}\n```\n\nIt's interesting noting that a `save` button is not necessary because Swift tracks changes to the model, so I left it off with the message _Changes are saved automatically_ in the form's footer.\n\nThen following Paul's instructions, I've added a simple `NavigationLink` to the ProspectsView as below:\n\n```swift\nNavigationLink(destination: EditProspectView(prospect: prospect)) {\n    VStack(alignment: .leading) {\n        \n        Text(prospect.name)\n            .font(.headline) + (title == \"Everyone\" \u0026\u0026 prospect.isContacted ? Text(\" \") + Text(Image(systemName: \"checkmark.seal.fill\")) : Text(\"\"))\n        Text(prospect.emailAddress)\n            .foregroundStyle(.secondary)\n        \n        \n    }\n    .swipeActions {\n        Button(\"Delete\", systemImage: \"trash\", role: .destructive){\n            modelContext.delete(prospect)\n        }\n        \n        if prospect.isContacted {\n            Button(\"Mark Uncontacted\", systemImage: \"person.crop.circle.badge.xmark\") {\n                prospect.isContacted.toggle()\n            }\n            .tint(.blue)\n        } else {\n            Button(\"Mark Contacted\", systemImage: \"person.crop.circle.fill.badge.checkmark\") {\n                prospect.isContacted.toggle()\n            }\n            .tint(.green)\n            \n            Button(\"Remind Me\", systemImage: \"bell\") {\n                addNotifications(for: prospect)\n            }\n            .tint(.orange)\n        }\n    }\n    .tag(prospect)\n}\n```\n\nHere's the result of the completed challenge:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/challenge_2.gif\" width=\"300\"/\u003e\n\u003c/div\u003e\n\n### Challenge 3\n\n\u003eAllow users to customize the way contacts are sorted – by name or by most recent.\n\nI completed this challenge by taking the following steps:\n\n1. Created a new variable to hold the sorted state. It makes sense to be a `Bool` since we have only two sorting options:\n\n```swift\n@State private var sortByName: Bool = true  // by name is the default option\n```\n\n2. Replaced the `Edit` button (now redundant, after challenge 2) with a button that toggles bewtween two states: `Sort by Date` and `Sort by Name`(the default):\n\n```swift\nToolbarItem(placement: .topBarLeading) {\n    Button(sortByName ? \"Sort by Date\" : \"Sort by Name\") {\n        sortByName.toggle()\n    }\n}\n```\n\n3. Created a computed property that sorts the prospects by name or most recent date according to the boolean value in `sortByName`:\n\n```swift\nvar sortedProspects: [Prospect] {\n    sortByName ? prospects.sorted { $0.name \u003c $1.name } : prospects.sorted { $0.dateAdded \u003e $1.dateAdded }\n}\n```\n\n4. Finally, the `List` now reads from `sortedProspects` instead of the default array that loads with the view.\n\n```swift\nNavigationStack {\n            List(sortedProspects, selection: $selectedProspects) { prospect in\n// code continues\n```\n\nHere's the final result:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/challenge_3.gif\" width=\"300\"/\u003e\n\u003c/div\u003e\n\nOf course, the solution could have been more complete, for instance, the user could be given the option of sorting ascending and descending, but honestly, I didn`t feel like spending more time on this...\n\n\u003e\"I will always choose a lazy person to do a difficult job because a lazy person will find an easy way to do it.\" (Bill Gates)\n\n## Acknowledgments\n\nOriginal code created by: [Paul Hudson - @twostraws](https://x.com/twostraws) (Thank you!)\n\nMade with :heart: by [@cewitte](https://x.com/cewitte)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcewitte%2Fhotprospects","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcewitte%2Fhotprospects","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcewitte%2Fhotprospects/lists"}