この記事は何?
Develop in Swiftに追加された新しいチュートリアル「Custom types and Swift Testing」をやってみたので、備忘録として残しておきます。
「Model data with custom types」では、現実世界の概念をコードのモデルとして表現する方法を学びます。
さまざまなテクニックを駆使して、独自のSwift型でデータモデルを作成し、アプリを強化します。
Swiftを基礎から学ぶには
自著、工学社より発売中の「まるごと分かるSwiftプログラミング」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。
また、Swiftプログラミングを基礎から動画で学びたい方には、Udemyコース「今日からはじめるプログラミング」をお勧めします。
What you'll learn
- Swiftで独自のデータ型をモデル化して、現実世界の概念を表現する方法
- コードが期待どおりに機能することを確認するために、Swift Testingでテストを作成する方法
- SwiftDataの基本は、想像できるあらゆる種類のデータでアプリを強化するために使用できます。
- Xcodeプレビューを使用してデータモデルに関するインターフェイスを設計する方法
- 複雑なアプリ間でデータモデルを共有および調整する方法
About data modeling
データモデリングは、現実世界の概念をコードのモデルとして表現する手法です。
これについては、以前のチュートリアルでいくつかを経験済みです。
状態のあるUIの更新でサイコロを振るとき、単一の数字を使用してサイコロをモデル化しました。
同様に、動的コンテンツの作成で仲間のグループを文字列の配列としてモデル化しました。
これらのモデルはプロトタイプでは問題ありませんが、より複雑なアプリにはより複雑なデータが必要です。
複数の種類のデータをモデル化したい場合があります。
異なる種類のデータ間の関係をモデル化することもできます。
モデルをコードで表現することで、その複雑さの管理が容易になります。
各アプリは、アプリに適した方法でデータモデルを変換、表示、および変更します。
天気予報は整数を温度として扱い、ダイスローラーは整数をサイコロのパイプ柄として表示します。
ビジネスロジックとは、データが操作されるアプリ固有の方法を指します。
データモデリングにはデータ自体に加えて、ビジネスロジックが含まれます。
What you'll need
- SwiftUI の基礎でカバーされているXcode、Swift、およびSwiftUIの基礎に精通している
- macOS Sequoiaか、Sonomaを実行しているMac
Note:
iOSデバイスは必要ありません。
Xcodeには、Mac上でアプリの外観と動作を示すツールが含まれています。
Model data with custom types
ゲーム中のスコアを追跡するアプリを作成し、カスタム型がデータモデリングを容易にする方法を確認します。
1. Create an app with a list of players
デフォルトのプレイヤーリストを作成し、新しいプレイヤーを追加させます。
最初に、各プレイヤーを文字列としてモデル化します。
このモデルは、チュートリアルの後半で改善します。
import SwiftUI
struct ContentView: View {
@State private var players = ["Elisha", "Andre", "Jasmine"]
var body: some View {
VStack {
ForEach(0..<players.count, id: \.description) { index in
TextField("Name", text: $players[index])
}
Button("Add Player", systemImage: "plus") {
players.append("")
}
}
.padding()
}
}
2. Add scores
それぞれのプレイヤーごとにスコアが必要です。
これをモデル化するには、「整数がplayers
配列の要素名と同じ順に並んだ配列」を作成します。
アプリを構築し続けていき、データモデルの一貫性がなくなった際のクラッシュを特定して、修正します。
プレイヤーをモデル化する文字列は名前とスコアを同時に追跡できないので、スコアを個別にモデル化しています。
今のところ、これら2つの配列の同じインデックスに2つのデータが表示されることを確認することで、各プレイヤーの名前をスコアに関連付けます。
例えば、アンドレはplayers
配列のインデックス1
なので、スコアもscores
配列の1
になります。
Stepper
でもTextField
の名前で使用しているのと同じインデックスを使用しますが、プレビューがクラッシュすることに注意してください。
これは、モデルに一貫性がないために発生します。
プレイヤー名とスコアが一致するようにインデックスを使用する配列データを2つモデル化しましたが、players
配列には3つの要素がある一方、scores
配列は空です。
配列にないインデックスを読み取ろうとすると、「範囲外のインデックス」エラーでクラッシュします。
各プレイヤーのscores
配列に0
を追加してモデルの一貫性を修正してから、プレビューを更新します。
それぞれのプレイヤーに関連する名前とスコアが出来たので、モデルは一貫しています。
プレビューで追加ボタンをクリックすると、また「範囲外のインデックス」クラッシュが発生します。
追加ボタンをクリックすると、新しいプレイヤー名の空文字列が追加されますが、一致するスコア配列には何も追加されません。
データモデルは「スコアのないプレイヤーの作成」をサポートしていません。
players
配列に名前を追加するたびにscores
配列に0
を追加することで、クラッシュを修正できます。
プレビューの追加ボタンをクリックして、クラッシュが修正されたことを確認します。
このアプローチでプレイヤーをモデル化すると、クラッシュが繰り返し発生する問題を発見しました。
データに矛盾が生じて、一貫性がなくなりがちです。
import SwiftUI
struct ContentView: View {
@State private var players = ["Elisha", "Andre", "Jasmine"]
@State private var scores: [Int] = [0, 0, 0]
var body: some View {
VStack {
ForEach(0..<players.count, id: \.description) { index in
TextField("Name", text: $players[index])
Stepper("\(scores[index])", value: $scores[index])
}
Button("Add Player", systemImage: "plus") {
players.append("")
scores.append(0)
}
}
.padding()
}
}
3. Create your own player
プレイヤー名とスコアを別個の配列でモデル化すると、データモデルの一貫性は維持しにくくなることがわかりました。
対策として、プレイヤーに関するすべてのデータをモデル化する、独自のPlayer
型を作成します。
この手法によって、データは一貫性を常に維持できるようになります。
Player.swift
ファイルを作成します。
SwiftUIファイルとSwiftファイルの違いに注意してください。
プレイヤーファイルにbody
やプレビューはありません。
Player
構造体がデータを追跡して、ContentView
に表示します。
Player
型にIdentifiable
プロトコルを採用させます。
プロトコルはメソッド、プロパティ、その他の要件設計を定義します。
これらすべての要件を満たすことは、そのプロトコルに準拠することを意味します。
id
プロパティを定義して、Player
型をIdentifiable
プロトコルに準拠させます。
インスタンスのid
はプレイヤー名が変更されても影響を受けないので、SwiftUIは混乱せずに、複数のプレイヤーを同じ名前にできます。
SwiftUIはid
を使用して、モデルの変更とまったく新しいモデルの違いを識別します。
UUID
は「全般で固有な識別子」なので、これを使用すると各インスタンスのIDが一意であることを保証できます。
import Foundation
struct Player: Identifiable {
var id = UUID()
var name: String
var score: Int
}
コンテンツビューで、players
配列とscores
配列ではなく、Player
オブジェクトの配列にリファクタリングします。
scores
配列を削除し、players
配列を更新して、新しいPlayger
型インスタンスを保持します。
これにより発生するいくつかのコンパイルエラーには、以降の手順で対処します。
ここでは、名前とスコアがあるプレイヤーをモデル化しました。
したがって、数ステップ前のクラッシュと同様に、名前のみでプレイヤーを作成しようとすると、エラーが発生します。
ForEach
ループを更新して、$players
をイテレートします。
$
プレフィックスを使用すると、配列内の各プレイヤーにバインディングできます。
Player
はIdentifiable
に適合させてあるので、、ForEach
のid:
パラメータを指定する必要はなくなりました。
新しいPlayer
インスタンスを使用するには、TextField
のバインディングを更新します。
次に、Stepper
のバインディングを更新して、新しいプレイヤーインスタンスを使用します。
ボタンのaction:
クロージャにある2つのappend
メソッドを、Player
インスタンス作成に置き換えます。
シミュレーターでアプリを実行します。
スコア調整やプレイヤー追加を行って、データの一貫性が常に維持できることを確認してください。
import SwiftUI
struct ContentView: View {
@State private var players = [
Player(name: "Elisha", score: 0),
Player(name: "Andre", score: 0),
Player(name: "Jasmine", score: 0),
]
var body: some View {
VStack(alignment: .leading) {
ForEach($players) { $p in
TextField("Name", text: $p.name)
Stepper("\(p.score)", value: $p.score)
}
Button("Add Player", systemImage: "plus") {
let newOne = Player(name: "", score: 0)
players.append(newOne)
}
}
.padding()
}
}
4. Improve your app design
アプリのタイトルを追加します。
そして、Grid
ビューでUIを整理して、「プレイヤーの名前とスコア」を列とヘッダーに並べます。
ForEach
をGrid
ビューでラップします。
Grid
ビューとGridRow
ビューは、複数行のビューを水平に揃えるのに最適です。
これらのビューは連携して、コンテンツの各行が完璧な列に整列されます。
GridRow
ビューを使用して、TextField
ビューとStepper
ビューを列にします。
ForEach
の上にGridRow
を追加して、プレイヤーとスコア列のタイトル列を保持します。
.Headline
フォントを使用して、タイトルをより目立たせます。
プレイヤー列にText
ビューを追加してスコアを表示します。
これにより、Stepper
のラベルは冗長になるので、.labelsHidden()
で隠してください。
Stepper
ビューのラベルが可能な限りのスペースを占有するのとは対照的に、Text
ビューは必要なスペースのみを占有します。
この変更を行うと、プレイヤーのスコアの列がはるかに狭くなります。
.gridColumnAlignment
モディファイアをPlayers
ヘッダーに追加します。
プレイヤー名に合わせて、.leading
にしてください。
Grid
ビューに垂直方向の余白を追加します。
VStack
の最後にSpacer
ビューを追加して、全体を上にプッシュします。
import SwiftUI
struct ContentView: View {
@State private var players = [
Player(name: "Elisha", score: 0),
Player(name: "Andre", score: 0),
Player(name: "Jasmine", score: 0),
]
var body: some View {
VStack(alignment: .leading) {
Text("Score Kepper")
.font(.title)
.bold()
.padding(.bottom)
Grid {
GridRow {
Text("Player")
.gridColumnAlignment(.leading)
Text("Score")
}
.font(.headline)
ForEach($players) { $p in
GridRow {
TextField("Name", text: $p.name)
Text("\(p.score)")
Stepper("\(p.score)", value: $p.score).labelsHidden()
}
}
}
.padding(.vertical)
Button("Add Player", systemImage: "plus") {
let newOne = Player(name: "", score: 0)
players.append(newOne)
}
Spacer()
}
.padding()
}
}
What’s next?
チュートリアルの続きでは、ScoreKeeperアプリのコアロジックを表す別のカスタム型を定義します。
また、アプリが意図したとおりに機能することを証明するコードも作成します。
記事の続きは、こちら。