Your Second iOS & SwiftUI App

Nov 4 2021 · Swift 5.5, iOS 15, Xcode 13

Part 3: Managing Rows

26. Swipe Actions

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 25. Sections Next episode: 27. Delete & Move Rows

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 26. Swipe Actions

We’ve been able to add books to our library for a few episodes now.

But what if our cat eats one? What if we lose one? We need to be able to delete books from our library.

It would also be nice to be able to quickly mark a book as finished from the main List view. We can handle both of these with Swipe Actions!

Swipe actions can be added via modifiers to views within a List or ForEach. For our app, let’s start by adding a swipe action to mark books as “Read Me!” or “Finished”.

The right place to do that, now, is in SectionView, inside the ForEach, directly to each BookRow.

ForEach(books) { book in
          BookRow(book: book)
            🟩.swipeActions(edge: ######) { }

With this modifier, you can designate the edge of your swipe action. Think of it like, which edge of the view will this action be attached to?

Normally, in iOS apps, delete actions are on the trailing edge of rows. So, let’s put our readMe switching action on the leading edge, instead.

ForEach(books) { book in
          BookRow(book: book)
            .swipeActions(edge: 🟩.leading) { }

We’ll ignore “allowsFullSwipe”. It’s true by default, which is fine for us. Next we define the body. This will normally be a button.

              🟩Button {
                
              } label: {
                
              }

This will work similar to the bookmark button in the detail view, but not exactly the same.

So, for the action, toggle the readMe property of this row’s book. And then immediately call Library.sortBooks

              Button {
                  🟩book.readMe.toggle()
                  library.sortBooks()🟥
              } label: {

Just like with Detail View, that will force SwiftUI to recognize a change in the Library data, and update Views accordingly. To make that change animated, wrap this in withAnimation

              Button {
                🟩withAnimation {
                  book.readMe.toggle()
                  library.sortBooks()
                🟩}

For the button’s label, we’ll use bookmark SF symbols again. If the book’s readMe property is true

} label: {
  🟩book.readMe
  ?

Use a Label with “Finished” for the title, and “bookmark.slash” for the image.

? 🟩Label("Finished", systemImage: "bookmark.slash")

but if readMe is false, title it “Read Me!”, and use the regular “bookmark”

? Label("Finished", systemImage: "bookmark.slash")
🟩: Label("Read Me!", systemImage: "bookmark")

Now this will visually read as a button to “unbookmark” a book in the ReadMe section, and to “bookmark” one in the “Finished” section.

And that’s all it takes to set up a swipe action! Try it out in a live preview. “Swipe” by clicking and dragging on a row from leading edge to trailing.

And that should move a book down into the “finished” section.

To take a better look at that button, “Swipe” by clicking and dragging from the leading edge to the middle area, then let go.

This is the default appearance for a swipe action with a Label for its view. So, let’s see how we can make it match our app a bit better.

First, we can adjust the color of the button with the tint modifier. Let’s set it to be the accent color

                : Label("Read Me!", systemImage: "bookmark")
              }
              🟩.tint(.accentColor)

By default, labels for swipe actions will appear with the title underneath the icon. But we can ask for just the icon with the labelStyle modifier.

We’ll want to apply that to all labels within this ForEach

        }
        .labelStyle(.iconOnly)
      } header: {

Now if you live preview and “swipe”. You’ve got a nice accent-colored button. We can add a swipe action to delete a book in the same way.

Add a new swipe action modifier just below the first one, this time using the trailing edge.

.swipeActions(edge: .trailing) {

For the view, add another button. And for this one, use the destructive role, just like we did for the confirmation dialog.

Button(role: .destructive) {

} label: {

}

The label can say “Delete” with a trash system image.

} label: {
  🟩Label("Delete", systemImage: "trash")
}

For the action, we want to delete the book from the Library’s data, but we don’t currently have a way to do that. So, leave yourself a TODO comment…

Button(role: .destructive) {
  🟩// TODO: Delete book
} label: {

And head over to Library.swift. I’m going to open it up with Shift-Command-O

We’ll add a “deleteBooks” method, and let’s put it right after the addNewBook method.

  func deleteBooks() {

  }

We’re going to do two things in here. First, remove the book from our Library entirely. And second, remove the image for that book, if there is one.

  func deleteBook() {
    // TODO: Remove Book
    // TODO: Remove Image
  }

There are a few ways you could go about these tasks, but to better plan this Delete method, it will be best if we know all of the places we might use it.

We’ve got a swipe action planned, but in the next episode we’re going to add an edit button which will let us delete in a slightly different way.

Let’s see if looking into that second case will give us some direction for our delete method.