[SwiftUI] SwiftUI の List で selection が有効にならないときにチェックすること

SwiftUI

SwiftUI の List で selection を使うときにハマった点を 解決策と一緒に説明します。

環境&対象

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

  • macOS Big Sur 11.1
  • Xcode 12.3
SwiftUI[SwiftUI] Listでselectionを有効にする方法

概要

SwiftUI の List は、イニシャライザで selection を指定することで、選択可能なリストになります。

iOS では、editMode を設定する必要がありますが、macOS では、editMode を設定することなく選択可能になります。

しかし、selection を指定しても選択できないリストになることがあり困り果てていたのですが、解決策が見つかったので、説明します。

# 以下のサンプル等は、macOS 上で確認していますが、iOS 上でも同じ制限があると思われます(未確認)

よく 例題として提示されているコード

StringSelectionList
StringSelectionList
StringListView

//
//  StringListView.swift
//
//  Created by : Tomoaki Yagishita on 2021/02/01
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct StringListView: View {
    // (1)
    @State private var selection:String? = nil
    // (2)
    var demoData = ["text1", "text2", "text3"]

    var body: some View {
        VStack {
            // (3)
            List(demoData, id:\.self, selection: $selection) { name in
                Text(name)
            }
            // (4)
            if let selection = selection {
                Text("selected text is \(selection)")
            }
        }
            .padding()
    }
}
コード解説
  1. リストで選択された値を保持する変数です
  2. デモ用のデータ
  3. initializer に selection も指定しているので、選択できるようになります
  4. 選択されているのであれば、選択されたテキストを表示

普通に動きます。

すこし応用編の選択できるリストビュー

アプリの開発を進めていくと、単純な型のリストを使うことは減ってきて、struct や class を使ったリストに変わっていきます。

そこで、以下のように struct をリスト表示しようとして問題が発生しました。

StructListView

//
//  StructListView.swift
//
//  Created by : Tomoaki Yagishita on 2021/02/01
//  © 2021  SmallDeskSoftware
//

import SwiftUI

// (1)
struct MyStruct: Identifiable, Hashable {
    var id: String
}

struct StructListView: View {
    // (2)
    @State private var selectedStruct: MyStruct? = nil
    // (3)
    var demoStructs = [MyStruct(id: "0"), MyStruct(id: "1"), MyStruct(id: "2")]

    var body: some View {
        VStack {
            // (4)
            List(demoStructs, id:\.id, selection:$selectedStruct) { element in
                Text(element.id)
            }
        }
        .padding()
    }
}
コード解説
  1. 例題用にシンプルな struct を作成しています
  2. 選択された要素を保持する変数
  3. デモ用のデータ
  4. struct に id プロパティを持たせているので id には、id を指定しています

上記のコードでリストは期待通りに表示されます。しかし、要素の選択ができません。

難しい仕組みを使っているつもりはなかったので、ここでハマりました。

選択できない理由は、id と selection の型が異なるためでした

ドキュメントに明確な記載は無いと思うのですが、List の引数である id と selection は、同じ型を対象としていないと選択動作ができなくなるようです。

StructSelectionList
StructSelectionList
struct と String それぞれを選択要素とする List

//
//  StructListView.swift
//
//  Created by : Tomoaki Yagishita on 2021/02/01
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct MyStruct: Identifiable, Hashable {
    var id: String
}

struct StructListView: View {
    // (1)
    @State private var selectedString:String? = nil
    // (2)
    @State private var selectedStruct: MyStruct? = nil
    var demoStructs = [MyStruct(id: "0"), MyStruct(id: "1"), MyStruct(id: "2")]

    var body: some View {
        VStack {
            // (3)
            List(demoStructs, id:\.id, selection:$selectedString) { element in
                Text(element.id)
            }
            Text("selected String : \(selectedString ?? "not selected")")

            // (4)
            List(demoStructs, id:\.self, selection:$selectedStruct) { element in
                Text(element.id)
            }
            Text("selected struct : \(selectedStruct?.id ?? "not selected")")
        }
        .padding()
    }
}
コード解説
  1. id として String を使う List での選択を保持する変数として String 型で定義
  2. id として MyStruct を使う List では保持する変数として、MyStruct 型の定義
  3. id として String 型、selection として、String? 型を指定
  4. id として MyStruct 型、selection として、MyStruct? 型を指定

上記の組み合わせでは期待する選択動作ができます。

うまく動かないケースでは、id として、String 型が指定され、selection として MyStruct? 型が指定されています。

この動作から、id として指定されている方法で要素を認識し、選択された場合その値を selection に設定するという動作だと推測します。

うまく動かないケースでは、id と selection で型が一致するように指定されていなかったために、選択操作をしても結果として選択用の変数に保存できないというのが背景にあった問題だと思います。

わかってみれば 簡単ですが、わかるまでに、半日以上使いました・・・

まとめ:SwiftUI の List での選択動作が動かないときに確認すること

SwiftUI の List での選択動作が動かないときに確認すること
  • id と selection の型が一致するか再確認

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

コメントを残す

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