SwiftUI・List・Core Dataでソート機能を実装する
Table of Contents
SwiftUIのListで表示しているデータを、ユーザーが並べ替えできるようにする方法をまとめます。
なお、参考にしたリソースは以下のとおりです。
- core data - SwiftUI reorder CoreData Objects in List - Stack Overflow
- SOLVED: Move rows in a list is not working – SwiftUI – Hacking with Swift forums
- [Swift] MutableCollection.move を理解する | SmallDeskSoftware
- onMove(perform:) | Apple Developer Documentation
実装手順 #
- ソートしたいCore DataのEntityに、ソート順を記憶するAttributeを追加する
- ソート順でデータが並ぶように、FetchRequestのsortDiscriptorを変更する
onMove
モディファイアで並べ替えイベントを受け取り、データを並べ替える
解説 #
ソート順を記憶するAttributeを追加 #
まず、ソート機能を追加するCore DataのEntityに、ソート順を記憶するAttributeを追加します。私の場合は、以下のように設定しました。
- Name: userOrder
- Type: Integer 16
- Optional: チェック無し
- Default Value: 0
- Validation: Minimum 0
sortDescriptorsを変更 #
次に、Fetch RequestのsortDescriptorsを変更します。ここでは、SwiftUIの@FetchRequest
を変更する例を示します。
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(keyPath: \MyItem.userOrder, ascending: true), // この行を追加
NSSortDescriptor(keyPath: \MyItem.createdAt, ascending: false)
],
animation: .default
)
private var items: FetchResults<MyItem>
このように、userOrder
のSort Descriptorを最優先で記述することで、ユーザーの並べ替えた順序をリストに反映することができます。
ascending: true
で指定するため、実際の画面では、リストの下へ行くにつれてuserOrder
の値が大きくなるように並びます。
onMove
モディファイアを設置する #
Listをソートできるようにするには、.onMove
モディファイアを追加します。
これを追加することで、並べ替え対象のアイテムと、移動先のオフセットを取得することができるようになります。
まず、もしList
だけでUIを実装していた場合は、ForEach
で並べるようにUIを修正します。
これは、.onMove
がDynamicViewContent
プロトコルに定義されており、それに準拠したViewがForEach
だからです。
var body: some View {
List {
ForEach(items) { item in
...
}
.onMove(perform: moveItems)
}
.onMove(perform:)
の引数は、Optional<(IndexSet, Int) -> Void>
です。
並べ替えのイベントを受け取るには、IndexSetとIntを受け取るメソッドを渡します。
private func moveItems(_ indexSet: IndexSet, offset: Int) {
}
データを並べ替えて保存する #
並べ替えのイベントが受け取れるようになったので、最後は、データを並べ替えて保存します。
private func moveItems(_ indexSet: IndexSet, offset: Int) {
// CoreDataのfetchResultから、Arrayに変換する
var revisedItems = items.map { $0 }
// データを並べ替える
revisedItems.move(fromOffsets: indexSet, toOffset: offset)
// 0からアイテム数-1までのインデックスを作る
let indices = stride(from: revisedItems.count - 1, through: 0, by: -1 )
// 並べ替え順の値を、全てのデータにセットする
for reverseIndex in indices {
revisedItems[reverseIndex].userOrder = Int16(reverseIndex)
}
// CoreDataの変更を保存する
save()
}
}
CoreDataのfetchResultは、そのままではIndexSetで並べ替えることができません。
そのため、いったん配列化してから、順序の変更や値の変更を行います。
配列化しても、map
でManagedObjectインスタンスそのものを返しているので、引き続き各インスタンスはViewContextで管理されています。
あとは、通常通りsaveするだけです!
以上で、Core Dataを使ったList(SwiftUI)の、並べ替え機能の実装は終わりです。