[SwiftUI] Table の使い方

SwiftUI2021

     
⌛️ 4 min.
SwiftUI の Table の使い方をまとめます。

環境&対象

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

  • macOS14.0 Sonoma
  • Xcode 15
  • iOS 17
  • Swift 5.9

シンプルな Table の使い方

はじめに、シンプルな Table の使い方を再確認します。

# サンプルは、Apple のドキュメントからのデータです。

Column を KeyPathで表示

Table は、複数の TableColumn から構成される View です。

その TableColumn を指定する時に、データに対しての KeyPath を指定することができます。

ここでは、Person というデータを各行に表示していますが、各 TableColumn に表示する値を KeyPath で指定しています。
なお、KeyPath は、String を指すことが必要です。
(String を指していない時は次に説明する View を指定することが必要となります。)

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2023/09/22
//  © 2023  SmallDeskSoftware
//

import SwiftUI

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()

    var fullName: String { givenName + " " + familyName }
}


let people = [
    Person(givenName: "Juan", familyName: "Chavez", emailAddress: "juanchavez@icloud.com"),
    Person(givenName: "Mei", familyName: "Chen", emailAddress: "meichen@icloud.com"),
    Person(givenName: "Tom", familyName: "Clark", emailAddress: "tomclark@icloud.com"),
    Person(givenName: "Gita", familyName: "Kumar", emailAddress: "gitakumar@icloud.com")
]

struct ContentView: View {
    var body: some View {
        Table(people) {
            TableColumn("Given", value: \.givenName)
            TableColumn("FaimlyName", value: \.familyName)
            TableColumn("Email", value: \.emailAddress)
        }
    }
}

#Preview {
    ContentView()
}

以下のような表示となります。

TableWithKeyPath

Column に View を指定

表示したい値を KeyPath で直接指定できない時は、TableColumn に View を指定することができます。
例えば、FamilyName をすべて大文字で表示したい時には、表示データ作成に何らかの処理が必要となるので KeyPath でプロパティを直接指定することができません。このような時には、以下のように View を指定して TableColumn に表示することが可能です。

以下では、familyName というプロパティ値を uppercased したものを Text を使って表示するように指定しています。

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2023/09/22
//  © 2023  SmallDeskSoftware
//

import SwiftUI

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()


    var fullName: String { givenName + " " + familyName }
}


let people = [
    Person(givenName: "Juan", familyName: "Chavez", emailAddress: "juanchavez@icloud.com"),
    Person(givenName: "Mei", familyName: "Chen", emailAddress: "meichen@icloud.com"),
    Person(givenName: "Tom", familyName: "Clark", emailAddress: "tomclark@icloud.com"),
    Person(givenName: "Gita", familyName: "Kumar", emailAddress: "gitakumar@icloud.com")
]

struct ContentView: View {
    var body: some View {
        Table(people) {
            TableColumn("Given", value: \.givenName)
            //TableColumn("FaimlyName", value: \.familyName)
            TableColumn("FAMILYNAME", content: { people in
                Text(people.familyName.uppercased())
            })
            TableColumn("Email", value: \.emailAddress)
        }
    }
}


#Preview {
    ContentView()
}

以下のような表示となります。

TableWithView

確認: Table は、Lazy である

Table は、List と似ているように思えるのですが、大きく違う点があります。

List は、Lazy ではありませんが、Table は Lazy です。

つまり、List は List 自身を作成するタイミングで 渡された要素すべての View を 作成しようとしますが、Table は必要になる部分の View のみを作成します。
# ドキュメントにはどこにも書いていない気がします・・・・

以下の Table で試してみます。

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2023/09/22
//  © 2023  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var items = (1..<2000).publisher.map({ TableItem($0) }).sequence
    var body: some View {
        Table(items, columns: {
            TableColumn("No") { item in
                Text(String(item.index))
            }
            TableColumn("Value") { item in
                TableRowView(item)
            }
        })
    }
}

struct TableItem: Identifiable {
    let id = UUID()
    let index: Int
    var value: String
    init(_ index: Int) {
        self.index = index
        self.value = String(index)
    }
}

struct TableRowView: View {
    let item: TableItem
    init(_ item: TableItem) {
        self.item = item
        print("init for \(item.value)")
    }
    var body: some View {
        Text("No. \(item.index) Value: \(item.value)")
    }
    
}

#Preview {
    ContentView()
}

実行すると以下のようになります。
Xcode の ログをみると 17番目までしか View が init されていないことがわかります。

LazyCheck

この例では、2000個の要素を 用意していますが、View として init されたのは、17とわかります。
(表示範囲に見えている要素は 16 までです。)

もちろん、スクロールすると 以降の View も init されていきます。

List を使用する時には、要素数を考慮して、List か LazyVStack かを検討しなければいけませんでしたが、Table は、Lazy に構築されるようですので、そのような心配は不要のようです。

# 繰り返しですが、Apple のドキュメントには、Lazy であるという説明はありません。

Sort できる Table

macOS のアプリでは TableColumn のタイトルをクリックすることで、その列の情報でソートできるようになっていることがあります。(ソートできる”べき”かどうかは別の話なので、そのような実装でないアプリもあります)

SwiftUI の Table でもそのようなソートを実装することができます。

注意

Table が データをソートすることはありません。あくまで ソートのきっかけを教えてくれるだけなので、条件に応じてソートするのは、Table を使う側の責務です。

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2023/09/22
//  © 2023  SmallDeskSoftware
//

import SwiftUI

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()

    var fullName: String { givenName + " " + familyName }
}

struct ContentView: View {
    @State private var people = [
        Person(givenName: "Juan", familyName: "Chavez", emailAddress: "juanchavez@icloud.com"),
        Person(givenName: "Mei", familyName: "Chen", emailAddress: "meichen@icloud.com"),
        Person(givenName: "Tom", familyName: "Clark", emailAddress: "tomclark@icloud.com"),
        Person(givenName: "Gita", familyName: "Kumar", emailAddress: "gitakumar@icloud.com")
    ]
    // (1)
    @State var sortOrder: [KeyPathComparator] = [KeyPathComparator(\Person.givenName, order: .forward)]
    var body: some View {
        // (2)
        Table(people, sortOrder: $sortOrder) {
            TableColumn("Given", value: \.givenName)
            // (3)
            TableColumn("FAMILYNAME", value: \.familyName, content: { people in
                Text(people.familyName.uppercased())
            })
            TableColumn("Email", value: \.emailAddress)
        }
        // (4)
        .onChange(of: sortOrder) { _, newValue in
            people.sort(using: newValue)
        }
    }
}

#Preview {
    ContentView()
}
  1. ソート指定を保持する変数を定義します 。初期値は、givenName でのソートにしています(特に givenName に設定した意味はありません。好きに指定できます。)
  2. Table に sortOrder への引数として、sortOrder を渡す。ユーザー操作によりソート指定が変更された時に、この変数が更新されます。この変数を見ることで その時点でのソート条件がわかります。
  3. View で指定しているケースでは、ソート対象の 値を定義することが必要です。ここでは value: で指定しています。
  4. ソート指定の変更を検知して、データをソートします

以下のような動作になります。

選択できる Table

List と同じように、Table でも 行を選択することができます。

選択できる数が “1つ”, “複数” のどちらも指定できるところも同じです。

selection に Set か、Optional<ID> のいずれの Binding を渡すことで、それぞれ指定できます。

以下は、1つ選択できる Table にしています。

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2023/09/22
//  © 2023  SmallDeskSoftware
//

import SwiftUI

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()
    let age: Int = Int.random(in: 10...50)


    var fullName: String { givenName + " " + familyName }
}

struct ContentView: View {
    @State private var people = [
        Person(givenName: "Juan", familyName: "Chavez", emailAddress: "juanchavez@icloud.com"),
        Person(givenName: "Mei", familyName: "Chen", emailAddress: "meichen@icloud.com"),
        Person(givenName: "Tom", familyName: "Clark", emailAddress: "tomclark@icloud.com"),
        Person(givenName: "Gita", familyName: "Kumar", emailAddress: "gitakumar@icloud.com")
    ]
    
    @State private var selection: Person.ID? = nil

    var body: some View {
        Table(people, selection: $selection) {
            TableColumn("Given", value: \.givenName)
            TableColumn("FAMILYNAME", content: { people in
                Text(people.familyName.uppercased())
            })
            TableColumn("Email", value: \.emailAddress)
        }
    }
}

#Preview {
    ContentView()
}

以下のような動作になります。

macOSであれば、⌘キーを押下してクリックすることで、選択を取り消すことができます。

Column を並べ替えられる Table

あまり使ったことがない人もいるかもしれませんが、macOS のアプリでは、Table の Column をドラッグで入れ替えられるようになっているアプリがあります。(並べ替えが便利かどうかは、アプリ依存なので、そのような機能が実装されていないアプリも多いです)

SwiftUI の Table でも、実装することで、そのような動作をサポートすることができます。

そのために TableColumnCustomization という型が用意されています。

Apple のドキュメントは、こちら

並べ替えられた Column を記憶するための型です。

なお、並び替えられる Column には、.customizationID を指定することが必要です。

Apple のドキュメントは、こちら

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2023/09/22
//  © 2023  SmallDeskSoftware
//

import SwiftUI

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()

    var fullName: String { givenName + " " + familyName }
}

struct ContentView: View {
    @State private var people = [
        Person(givenName: "Juan", familyName: "Chavez", emailAddress: "juanchavez@icloud.com"),
        Person(givenName: "Mei", familyName: "Chen", emailAddress: "meichen@icloud.com"),
        Person(givenName: "Tom", familyName: "Clark", emailAddress: "tomclark@icloud.com"),
        Person(givenName: "Gita", familyName: "Kumar", emailAddress: "gitakumar@icloud.com")
    ]

    @State private var columnCustomize: TableColumnCustomization = TableColumnCustomization()
    var body: some View {
        Table(people, columnCustomization: $columnCustomize) {
            TableColumn("Given", value: \.givenName)
                .customizationID("given")
            TableColumn("FAMILYNAME", content: { people in
                Text(people.familyName.uppercased())
            })
            .customizationID("family")
            TableColumn("Email", value: \.emailAddress)
        }
    }
}

#Preview {
    ContentView()
}

以下のような動作になります。

まとめ

SwiftUI の Table でできることを確認しました。

SwiftUI の Table でできること
  • KeyPath を使って、表示することができる
  • 独自 View を使って表示することもできる
  • Table は、子要素を Lazy に作成表示する
  • Table に、Sort を実装できるが、Sort そのものは自分で実装する必要がある
  • Table は、List と同様に 単一選択・複数選択が指定できる
  • Table の Column を D&D で並べ替えることもできる

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

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMatery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

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