[SwiftUI][macOS] SwiftUI の List/ForEach での onMove 問題の回避策

SwiftUI

SwiftUI の List を onMove 付きで macOS で使う時に発生する問題点と回避策を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.1
  • Xcode 12.3

SwiftUI の List と onMove, onDelete

SwiftUI の List を ForEach + onMove, onDelete と組み合わせて使うと非常に便利です。

List 使用例 (iOS)

struct ContentView: View {
    @State private var data = ["Item1", "Item2", "Item3", "Item4"]
    var body: some View {
        VStack {
#if os(iOS)
            EditButton()
#endif
            List {
                ForEach(data, id:\.self) { title in
                    HStack {
                        Text("Title: \(title)")
                    }
                }
                .onDelete(perform: { indexSet in
                    data.remove(at: indexSet.first!)
                })
                .onMove(perform: { indices, newOffset in
                    data.move(fromOffsets: indices, toOffset: newOffset)
                })
            }
        }
    }
}

上記のコードで、以下のような 要素の移動や削除をできる UI を持つリストを作ることができます。

macOS 上での List と onMove, onDelete

List という View も ForEach の onMove, onDelete いずれも、macOS 用にも用意されています。

先のコードは、macOS にも使えるので、動かしてみると以下のような動作になります。

削除はうまくできるのですが、要素を移動すると、行の高さがおかしくなります。

macOS 上での List/ForEach と onMove の問題点

上の動画で確認できる通り、「要素移動させると、行の高さがおかしくなる」が問題です。

当面の回避策

修正を待っていてもしょうがないので、回避策をさがしてみました。

.frame で適正な高さを設定

そのままですが、.frame で height に値を設定してしまうのが、確実です。

ただ、その値を計算するのが手間で、システム側で適切に設定して欲しいというケースが多いです。
なので、.frame で height を計算することを避けたいので、他を探してみました。

.frame で 幅を指定しても OK

理由は不明ですが、.frame で width に値を設定しても期待通りの動きになります。

width 指定したコード

struct ContentView: View {
    @State private var data = ["Item1", "Item2", "Item3", "Item4"]
    var body: some View {
        VStack {
#if os(iOS)
            EditButton()
#endif
            List {
                ForEach(data, id:\.self) { title in
                    HStack {
                        Text("Title: \(title)")
                            .frame(width: 300)    // 追加!
                    }
                }
                .onDelete(perform: { indexSet in
                    data.remove(at: indexSet.first!)
                })
                .onMove(perform: { indices, newOffset in
                    data.move(fromOffsets: indices, toOffset: newOffset)
                })
            }
        }
    }
}

なぜ width を指定することで、height が正しく調整されるのかは不明ですが、高さも元の高さを保って移動されます。

まとめ:macOS の List/ForEach の onMove の行高さ不具合を回避する方法

macOS の List/ForEach の onMove の行高さ不具合を回避する方法
  • .frame で height を指定する
  • .frame で width を指定しても高さを保持して、移動される

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です