Sponsor Link
環境&対象
- 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()
}
以下のような表示となります。

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()
}
以下のような表示となります。

確認: 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 されていないことがわかります。

この例では、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()
}
- ソート指定を保持する変数を定義します 。初期値は、givenName でのソートにしています(特に givenName に設定した意味はありません。好きに指定できます。)
- Table に sortOrder への引数として、sortOrder を渡す。ユーザー操作によりソート指定が変更された時に、この変数が更新されます。この変数を見ることで その時点でのソート条件がわかります。
- View で指定しているケースでは、ソート対象の 値を定義することが必要です。ここでは value: で指定しています。
- ソート指定の変更を検知して、データをソートします
以下のような動作になります。
選択できる 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 でできることを確認しました。
- 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 もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
販売元のページは、こちらです。
SwiftUI 徹底入門
# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link