【SwiftUI】 Table 2025
2025年(春の訪れのほんの少し前)におけるSwiftUIの Table
の仕様をまとめる。
Table とは
表を作れるもの。
用語
Row 行
横向きに切り裂いた時のそれぞれの塊
Column 列
縦向きに切り裂いた時のそれぞれの塊
仕様
- iPhoneなど横幅が狭いときは全部表示しない これはサイズクラスの横がコンパクトのときにおこる
- 行を選択可能。ひとつのみ/複数の両方に対応
- 行をソート可能。ソートネタの列を指定する方式
- 計算型のセルが可能
コードの共通部分
以下のものを共通で使用する
struct Person: Identifiable {
let givenName: String
let familyName: String
let emailAddress: String
let id = UUID()
var fullName: String { givenName + " " + familyName }
}
//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")
]
基本
import SwiftUI
struct Person: Identifiable {
let givenName: String
let familyName: String
let emailAddress: String
let id = UUID()
var fullName: String { givenName + " " + familyName }
}
struct PeopleTable: 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")
]
var body: some View {
Table(people) {
TableColumn("Given Name", value: \.givenName)
TableColumn("Family Name", value: \.familyName)
TableColumn("E-Mail Address", value: \.emailAddress)
}
}
}
iPhone | iPad |
---|---|
![]() |
![]() |
選択
ある行を選択可能にする。
Set
を渡すと複数選択可能。
単一の選択なら0個選択に対応するためにoptionalで宣言する。
struct SelectableTable: View {
@State private var selectedPeople = Set<Person.ID>()
//単一の選択なら @State private var selectedPeople: Person.ID?
@State private var people = //略
@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")
]
var body: some View {
Table(people, selection: $selectedPeople) {
TableColumn("Given Name", value: \.givenName)
TableColumn("Family Name", value: \.familyName)
TableColumn("E-Mail Address", value: \.emailAddress)
}
Text("\(selectedPeople.count) people selected")
}
}
iPhone | iPad |
---|---|
![]() |
![]() |
ソート
どれかの列をソートのネタにする。
テーブルのイニシャライザに sortOrder:
を渡す。
sortOrder:
が渡されていればソートする。かつ、他のソート方式にも操作できる。
表示した時点で sortOrder を適用させるには initial: true
「ソースコードの中からソート方式を変化させる」に対応するには onChange
を使用する。
struct SortableTable: View {
@State private var sortOrder = [KeyPathComparator(\Person.givenName)]
@State private var people = //略
var body: some View {
Table(people, sortOrder: $sortOrder) {
TableColumn("Given Name", value: \.givenName)
TableColumn("Family Name", value: \.familyName)
TableColumn("E-Mail address", value: \.emailAddress)
}
.onChange(of: sortOrder, initial: true) { _, sortOrder in
people.sort(using: sortOrder)
}
}
}
iPhone | iPad |
---|---|
![]() |
![]() |
iPadの方は列ヘッダーの横にソート状態を表示するものが付いています。
計算型セル
SwiftUIの言い方ではstatic rowsというらしい。
TableColumn
では値を設定する代わりに、計算クロージャを設定する。
各 TableRow
でネタを投下する。
struct Purchase: Identifiable {
let price: Decimal
let id = UUID()
}
struct TipTable: View {
let currencyStyle = Decimal.FormatStyle.Currency(code: "USD")
var body: some View {
Table(of: Purchase.self) {
TableColumn("Base price") { purchase in
Text(purchase.price, format: currencyStyle)
}
TableColumn("With 15% tip") { purchase in
Text(purchase.price * 1.15, format: currencyStyle)
}
TableColumn("With 20% tip") { purchase in
Text(purchase.price * 1.2, format: currencyStyle)
}
TableColumn("With 25% tip") { purchase in
Text(purchase.price * 1.25, format: currencyStyle)
}
} rows: {
TableRow(Purchase(price: 20))
TableRow(Purchase(price: 50))
TableRow(Purchase(price: 75))
}
.tableStyle(.automatic)
}
}
iPhone | iPad |
---|---|
![]() |
![]() |
横方向のサイズクラスがコンパクトのときに対応する
iPhoneをはじめとしてサイズクラスの横がコンパクトの場合(要は横が狭い)はセルを横方向に全部表示しない。また、カラムのヘッダーも表示しない。なので工夫する。下の例では、列がひとつしか表示しないのでひとつの場所に情報詰め込むようにしている。
struct CompactableTable: View {
#if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
private var isCompact: Bool { horizontalSizeClass == .compact }
#else
private let isCompact = false
#endif
@State private var sortOrder = [KeyPathComparator(\Person.givenName)]
@State private var people = //略
var body: some View {
Table(people, sortOrder: $sortOrder) {
TableColumn("Given Name", value: \.givenName) { person in
VStack(alignment: .leading) {
Text(isCompact ? person.fullName : person.givenName)
if isCompact {
Text(person.emailAddress)
.foregroundStyle(.secondary)
}
}
}
TableColumn("Family Name", value: \.familyName)
TableColumn("E-Mail Address", value: \.emailAddress)
}
.onChange(of: sortOrder) { _, sortOrder in
people.sort(using: sortOrder)
}
}
}
iPhone | iPad |
---|---|
![]() |
![]() |
TableColumn
TableColumn
の基本要素
- ヘッダーに書く文字列
- カラムに表示する内容
- そのカラムが(ネタに対して)なんであるか
TableColumn("Given name", value: \.givenName) { person in
Text(person.givenName)
}
上の場合、
ヘッダーに書くのは Given name
表示内容は Text(person.givenName)
そのカラムが(ネタに対して) \.givenName
であるとのことであるが、これはソートするときに使われる情報らしい。このカラムをソートするときは \.givenName
によってソートされる。
また、多くの場合、表示する内容と(ネタに対して)なんであるかは同一となるので次のコンビニエンスイニシャライザライザが使える。上のものと同一の内容である。
TableColumn("Given name", value: \.givenName)
感想
今までサイズクラスへの対応を全くしたことがない(サイズクラス使用以上に細かく設定する場合など)人でも、Table
を使う際は対応が必須になるのでは。
資料
Discussion