How to let users move rows in a list
How to let users move rows in a list 관련
Updated for Xcode 15
SwiftUI provides two ways to let us move items in a list: a simple way supported on iOS 16.0 or later, and a more advanced way that works on older iOS versions too. Regardless of which approach you choose, you can also selectively disable moving a row using the moveDisabled()
modifier.
The simple approach to moving works great if you're just moving your item around in an array, without adding any additional logic. To use it, use a data binding with your list and pass in the editActions
parameter, like this:
struct ContentView: View {
@State private var users = ["Glenn", "Malcolm", "Nicola", "Terri"]
var body: some View {
NavigationStack {
List($users, id: \.self, editActions: .move) { $user in
Text(user)
}
}
}
}
That immediately lets users drag the list rows around, and the users
array will be updated as they do so. If you want to add swipe to delete as well, use .all
rather than just .move
.
If you want to disable movement for one row, use moveDisabled()
with whatever criteria you have. For example, we could say that Glenn must always appear first in our list like this:
struct ContentView: View {
@State private var users = ["Glenn", "Malcolm", "Nicola", "Terri"]
var body: some View {
NavigationStack {
List($users, id: \.self, editActions: .move) { $user in
Text(user)
.moveDisabled(user == "Glenn")
}
}
}
}
For the more complex approach to moving, we can attach an onMove(perform:)
modifier to a ForEach
inside a list, and have it call a method of our choosing when a move operation happens. That method needs to accept a source IndexSet
and a destination Int
, like this:
func move(from source: IndexSet, to destination: Int) {
// move the data here
}
When moving several items it's always a good idea to move the later ones first so that you avoid moving other items and getting your indexes confused. Fortunately, Swift's sequences have a built-in way to move index sets for us, so we can just pass the parameters along and have it work correctly.
As an example, we could create a ContentView
struct that sets up an array of three username strings, and asks SwiftUI to move them around calling a move()
method. In order to activate moving – i.e., to make the drag handles appear – it also adds an edit button to the navigation stack so the user can toggle editing mode.
Here's the code:
struct ContentView: View {
@State private var users = ["Paul", "Taylor", "Adele"]
var body: some View {
NavigationStack {
List {
ForEach(users, id: \.self) { user in
Text(user)
}
.onMove(perform: move)
}
.toolbar {
EditButton()
}
}
}
func move(from source: IndexSet, to destination: Int) {
users.move(fromOffsets: source, toOffset: destination)
}
}