Skip to main content
  1. Posts/

SwiftUI・List・Core Dataでソート機能を実装する

SwiftUIのListで表示しているデータを、ユーザーが並べ替えできるようにする方法をまとめます。

なお、参考にしたリソースは以下のとおりです。

実装手順 #

  • ソートしたい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を修正します。 これは、.onMoveDynamicViewContentプロトコルに定義されており、それに準拠した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)の、並べ替え機能の実装は終わりです。