UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Letting the user mark favorites

Paul Hudson    @twostraws   

The final task for this project is to let the user assign favorites to resorts they like. This is mostly straightforward, using techniques we’ve already covered:

  • Creating a new Favorites class that has a Set of resort IDs the user likes.
  • Giving it add(), remove(), and contains() methods that manipulate the data, while also saving any changes to UserDefaults.
  • Injecting an instance of the Favorites class into the environment.
  • Adding some new UI to call the appropriate methods.

Swift’s sets already contain methods for adding, removing, and checking for an element, but we’re going to add our own around them we can call a save() method so the user’s changes are persisted. This in turn means we can mark the favorites set using private access control, so we can’t accidentally bypass our methods and miss out saving.

Create a new Swift file called Favorites.swift, replace its Foundation import with SwiftUI, then give it this code:

@Observable
class Favorites {
    // the actual resorts the user has favorited
    private var resorts: Set<String>

    // the key we're using to read/write in UserDefaults
    private let key = "Favorites"

    init() {
        // load our saved data

        // still here? Use an empty array
        resorts = []
    }

    // returns true if our set contains this resort
    func contains(_ resort: Resort) -> Bool {
        resorts.contains(resort.id)
    }

    // adds the resort to our set and saves the change
    func add(_ resort: Resort) {
        resorts.insert(resort.id)
        save()
    }

    // removes the resort from our set and saves the change
    func remove(_ resort: Resort) {
        resorts.remove(resort.id)
        save()
    }

    func save() {
        // write out our data
    }
}

You’ll notice I’ve missed out the actual functionality for loading and saving favorites – that will be your job to fill in shortly.

We need to create a Favorites instance in ContentView and inject it into the environment so all views can share it. So, add this new property to ContentView:

@State private var favorites = Favorites()

Now inject it into the environment by adding this modifier to the NavigationSplitView:

.environment(favorites)

Because that’s attached to the navigation split view, every view the navigation stack presents will also gain that Favorites instance to work with. So, we can load it from inside ResortView by adding this new property:

@Environment(Favorites.self) var favorites

Tip: Make sure you modify your ResortView preview to inject an example Favorites object into the environment, so your SwiftUI preview carries on working. This will work fine: .environment(Favorites()).

All this work hasn’t really accomplished much yet – sure, the Favorites class gets loaded when the app starts, but it isn’t actually used anywhere despite having properties to store it.

This is easy enough to fix: we’re going to add a button at the end of the scrollview in ResortView so that users can either add or remove the resort from their favorites, then display a heart icon in ContentView for favorite resorts.

First, add this to the end of the scrollview in ResortView:

Button(favorites.contains(resort) ? "Remove from Favorites" : "Add to Favorites") {
    if favorites.contains(resort) {
        favorites.remove(resort)
    } else {
        favorites.add(resort)
    }
}
.buttonStyle(.borderedProminent)
.padding()

Now we can show a colored heart icon next to favorite resorts in ContentView by adding this to the end of the label for our NavigationLink:

if favorites.contains(resort) {
    Spacer()
    Image(systemName: "heart.fill")
    .accessibilityLabel("This is a favorite resort")
        .foregroundStyle(.red)
}

Tip: As you can see, the foregroundStyle() modifier works great here because our image uses SF Symbols.

That finishes our project, so give it one last try and see what you think. Good job!

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.2/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.