iOS 27のreorderable container APIで並べ替えを試してみる
はじめに
iOS 27で追加された新APIの reorderable() / reorderContainer() の実際に使ってみる
並べ替えを実装する際の公式APIが実装されたのでより簡単に並べ替えができるようになった
セッションを見る限り、Listだけでなくいろんなコンテナに適用できるというものらしいのでUIの自由度が上がりそう
watch OSなどでも動作するみたい
検証環境
- Xcode 27 beta 6
- iOS 27.0 Simulator
※ベータ版での検証です。正式版までに仕様が変わる可能性があります。
ひとまずドキュメントを見てコードを書いてみる
import SwiftUI
import OrderedCollections
@main struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// 仮のデータ
struct Landmark: Identifiable {
let id = UUID()
let name: String
}
struct ContentView: View {
@State private var landmarks: [Landmark] = [
Landmark(name: "富士山"),
Landmark(name: "清水寺"),
Landmark(name: "姫路城"),
Landmark(name: "厳島神社"),
Landmark(name: "東京タワー"),
]
var body: some View {
VStack {
ForEach(landmarks) { landmark in
Text(landmark.name)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 8))
}
.reorderable() //新API
}
.reorderContainer(for: Landmark.self) { difference in
// 配列の更新を行う
difference.apply(to: &landmarks)
} //新API
.padding()
}
}
// 配列の並び替え
extension ReorderDifference where CollectionID == ReorderableSingleCollectionIdentifier {
func apply(to values: inout [some Identifiable<ItemID>]) {
var dictionary = OrderedDictionary(uniqueKeys: values.map { $0.id }, values: values)
let destinationOffset: Int? = switch destination.position {
case .before(let destination):
dictionary.keys.firstIndex(of: destination)
case .end:
nil
}
dictionary.move(keys: sources, to: destinationOffset ?? values.endIndex)
values = dictionary.values.elements
}
}
この辺りを参考に実装
WWDCの動画ではswift-collectionsを使っていると言っていたのでひとまずそれっぽいヘルパー関数を作ってみているが、正直どう実装するのが正解か分からない
動き

いい感じ
VStackでも問題なく並び替えができている!
別のコンテナに変えてみる
List
var body: some View {
List {
ForEach(landmarks) { landmark in
Text(landmark.name)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 8))
}
.reorderable()
}
.reorderContainer(for: Landmark.self) { difference in
// 配列の更新を行う
difference.apply(to: &landmarks)
}
.padding()
}

GIFでは分からないがアイテムを移動させたりするとちょっとカクつく時がある。動作はしてるが挙動がちょっと怪しい
LazyVGrid
private let columns = [GridItem(.adaptive(minimum: 100), spacing: 12)]
var body: some View {
LazyVGrid(columns: columns, spacing: 12) {
ForEach(landmarks) { landmark in
Text(landmark.name)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 8))
}
.reorderable()
}
.reorderContainer(for: Landmark.self) { difference in
difference.apply(to: &landmarks)
}
.padding()
}

こちらは良い感じに動作
触ってて気づいた問題
最初に公式ドキュメントにあるようにVStackで実装してみたが並び替え動作時に警告が出る
Adding 'UIView' as a subview of UIHostingController.view is not supported and may result in a broken view hierarchy. Add your view above UIHostingController.view in a common superview or insert it into your SwiftUI content in a UIViewRepresentable instead.

少し調べてみたがreorderContainerと関連付くこの警告の原因については情報が見当たらなかった
上記のVStack / LazyVGridの実装ではこの警告が出るがListでは出ない
ScrollViewで囲ったら警告が出なくなった
VStack / LazyVGridをScrollViewで囲うとこの警告はなくなることがわかった、前述のListでは警告が出なかったのはスクロール可能なViewだから出なかったのかなと推測が立ったが詳細は不明
以下回避策
var body: some View {
ScrollView {
VStack {
ForEach(landmarks) { landmark in
Text(landmark.name)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 8))
}
.reorderable()
}
.reorderContainer(for: Landmark.self) { difference in
difference.apply(to: &landmarks)
}
.padding()
}
}
まとめ
良いところ
- いろんなコンテナで並び替えができる
- アニメーションの実装なくシステム任せにできるのはめっちゃ楽
悪いところ
- コンテナによってはアイテムを掴んだり並び替えたりする時に引っ掛かりがある感じがある?
- 実際に手元で動かしてみてもらえれば分かると思う
良く分かんないこと
- 並び替えのヘルパー関数の実装だけど、これで良いのか???
- ちょっと挙動が怪しいところはあったがBetaなので今後改善されることを期待
- ベストプラクティス的なのがまだわかっていないし、情報が少ない
最後に
他にも検証してみたいことがあったり、あんまり深掘りできなかったけど気が向いたら別の記事で検証してみます!








