BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

Adding options with swipe actions

Paul Hudson    @twostraws   

We need a way to move people between the Contacted and Uncontacted tabs, and the easiest thing to do is add a swipe action to the VStack in ProspectsView. This will allow users to swipe on any person in the list, then tap a single option to move them between the tabs.

Now, remember that this view is shared in three places, so we need to make sure the swipe actions look correct no matter where it’s used. We could try and use a bunch of ternary conditional operators, but later on we’ll add a second button so the ternary operator approach won’t really help much. Instead, we’ll just wrap the button inside a simple condition – add this to the VStack now:

.swipeActions {
    if prospect.isContacted {
        Button("Mark Uncontacted", systemImage: "person.crop.circle.badge.xmark") {
            prospect.isContacted.toggle()
        }
        .tint(.blue)
    } else {
        Button("Mark Contacted", systemImage: "person.crop.circle.fill.badge.checkmark") {
            prospect.isContacted.toggle()
        }
        .tint(.green)
    }
}

Thanks to SwiftUI and SwiftData's tight integration, just calling toggle() on our property will flip the Boolean, update our view, and save the change to permanent storage – all in just one line of code.

If you run the app now you’ll see it works great now – scan a user, then bring up the context menu and tap its action to see the user move between the Contacted and Uncontacted tabs.

Now, swipe actions are a great feature for adding extra functionality to your SwiftUI app, but they don't play nicely with the onDelete() modifier we used previously. So, we need to make deletion work by hand, and we're going to do this in two ways at the same time: individual swipe to delete like we had with onDelete(), but also a multiple selection mode that lets users remove many entries at the same time.

Getting a swipe to delete equivalent is straightforward, because we can just add another button to our existing swipe actions. Make sure and add this first in the list of buttons, so that it automatically gets the "swipe fully to activate" functionality:

Button("Delete", systemImage: "trash", role: .destructive) {
    modelContext.delete(prospect)
}

Tip: When used by itself, the regular swipe to delete action uses the word "Delete" rather than a trash icon, but when used alongside other swipe actions Apple prefers to add the icon to avoid mixing icons and words.

The second approach we'll add is to let users select multiple rows at the same time and delete them in one go. That means adding some new local state to store their active selection:

@State private var selectedProspects = Set<Prospect>()

Then binding that selection to our list:

List(prospects, selection: $selectedProspects) { prospect in

Important: To help SwiftUI understand that each row in our List corresponds to a single prospect, it's important to add the following code after the swipe actions:

.tag(prospect)

Now we just need to decide what kind of UI to build to make selection and deletion possible. There are a few different options here, and of course you're welcome to experiment, but I think the easiest is to show an edit button the top-leading part of our navigation bar, which is where it normally lives in Apple's own apps. We can then activate mass deletion by adding a second button, and Apple usually has that button in a special toolbar at the bottom.

First, we need a method to call that will delete all the rows we selected:

func delete() {
    for prospect in selectedProspects {
        modelContext.delete(prospect)
    }
}

And now we can add two new toolbar items to the existing toolbar() modifier, one to create the edit button:

ToolbarItem(placement: .topBarLeading) {
    EditButton()
}

And then another one to create the delete button, but only when there are actually selections to delete:

if selectedProspects.isEmpty == false {
    ToolbarItem(placement: .bottomBar) {
        Button("Delete Selected", action: delete)
    }
}

With that in place we're giving users two ways to access the same thing: the regular swipe to delete they expect to see, but also a clear Edit/Done button to aid discoverability.

Important: Now that we're using ToolbarItem in our toolbar, you need to wrap the previous code in a ToolbarItem too, like this:

ToolbarItem(placement: .topBarTrailing) {
    Button("Scan", systemImage: "qrcode.viewfinder") {
        isShowingScanner = true
    }
}

Now go ahead and run the app – you should be able to add and delete prospects freely!

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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.5/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.