はじめに
本記事は、Apple公式のSwiftUI Tutorialsを参考として内容をまとめた記事です。
英語が苦手な方や、概要をさっと理解したい方を対象に本記事を執筆しております。
注釈コメントを入れたプロジェクトファイルはこちら(GitHub)
Creating and Combining Views
3つのコードエディタ
SwiftUIで記述するプログラムは、「ソースエディタ」「キャンバス」「インスペクタ」の3箇所から編集することができる。
ソースエディタ(source editor)
キャンバス(canvas)
インスペクタ(inspector)
インスペクタは、ソースエディタまたはキャンバスで、編集対象を⌘(command) + ⌥(control)を押したままクリックすると表示することができる。

ビュー(view)
ビューは、アプリケーション画面を構成する「画面部品」を指し、Viewプロトコルに準拠する構造体の内部で記述する。
また、ビュー毎に用意された**修飾子(modifier)**を付与することで、自由自在に変更することができる。
ただし、ビューがもつbodyプロパティは一つのビューしか返せないため、複数ビューをもたせる場合は後述するスタックでグループ化する必要がある。
基本的なビューと、その修飾子を以下に表す。
| ビュー(内容) | 修飾子 | 内容 |
|---|---|---|
| 共通 | foregroundColor | 色 |
| frame | サイズ | |
| padding | パディング | |
| Text(ラベル) | font | フォント |
| lineLimit | 表示行数 | |
| truncationMode | 省略位置 | |
| fixedSize | 水平・垂直方向のサイズ固定 | |
| Image(画像) | resizable | サイズの可変化 |
| aspectRatio | アスペクト比 | |
| renderingMode | 色の可変化 | |
| clipShape | フレームの図形 | |
| overlay | 上乗せするビュー | |
| shadow | 影の半径 | |
| Button(ボタン) | border | 枠線 |
| Path(2次元図形) | stroke | 線色 |
| fill | 塗りつぶし色 | |
| Spacer(スペーサー) | ||
| List(リスト) | ||
| Section(セクション) | ||
| NavigationView(ナビゲーションビュー) | navigationBarTitle | ナビゲーションバーのタイトル |
| NavigationLink(遷移先) | ||
| TabView(タブビュー) | tabItem | タブ |
| ScrollView(スクロールビュー) | ||
| TextField(テキストフィールド) | textFieldStyle | テキストフィールドのスタイル |
スタック(Stack)
スタックは、複数のビューをx, y, z方向にグループ化する。
主なスタックは、以下の通り。
| スタック | 方向 | 内容 |
|---|---|---|
| HStack | x | 全てのビューを水平方向にグループ化して表示 |
| LazyHStack | x | 画面内のビューを水平方向にグループ化して表示 |
| VStack | y | 全てのビューを垂直方向にグループ化して表示 |
| LazyVStack | y | 画面内のビューを垂直方向にグループ化して表示 |
| ZStack | z | 全てのビューを垂直方向にグループ化して表示 |
MapKit
MapKitは、ビューにマップを埋め込むのに用いるフレームワークであり、マップを表すMapビューと、マップに関連する各構造体を提供する。
座標(coordinate)
MapKitにおける座標は、座標を表すCLLocationCoordinate2Dと、縮尺を表すMKCoordinateSpanで構成されるMKCoordinateRegionで表現する。
値型データのバインド($)
値型データを代入する場合、通常は変数の値が代入されるが、参照する値型データに$を付与することで、変数として代入できる。
Building Lists and Navigation
ビュー(view), モデル(model), リソース(resource)
プロジェクトファイルの管理において、ファイルの用途・機能によって以下のように分類するのが望ましい。
| 分類 | 用途・機能 |
|---|---|
| ビュー | ビューを表すswiftファイル |
| モデル | ビューに反映するデータ構造を記述するswiftファイル |
| リソース |
データを格納・記述するファイル(jsonやXMLなど) |
JSON解析
JSON解析は、以下の手順で行う。
- Bundleからファイルパス(=
URLオブジェクト)を取得- Data型オブジェクトにJSONデータを格納
- JSON解析を行う
JSONDecoderクラスを用いたデコード処理の実施
ここで、JSONデータの解析結果を格納する構造体は、
DecodableとEncodableの2つの構造体を包含するCodableに準拠させることで、
JSONデータのキーと同名である構造体のプロパティに値を格納することができる。
また、JSONデータを格納した構造体リストを走査する場合は、Hashable・Identifiableプロトコルに準拠させる。
定義
// 1. Bundleからファイルパスを取得
Bundle.main.url(
forResource name: String?,
withExtension ext: String?
) -> URL?
// パラメータ
// name: リソースファイル名
// ext: ファイル拡張子(通常はnil)
// -> extをnilに設定した場合は、拡張子も含めてファイル名を記述
// 2. Data型オブジェクトの生成・格納
// -> 全項目イニシャライザ(memberwise initializer)によって、optionsパラメータの記述は不要
Data(contentsOf url: URL, options: Data.ReadingOptions = []) throws
// パラメータ
// url: ファイルパスを表すURLオブジェクト
// 3. JSONDecoderを用いたデコード処理
JSONDecoder#decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
// パラメータ
// type: 返却型
// -> 通常は"<返却型>.self"と記述し、<返却型>自身の型を指定
// data: JSONデータを格納するData型オブジェクト
サンプルコード(定型文)
// JSON解析処理
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
// ファイルパスの取得
// -> プロジェクトファイルはBundleに格納される
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
// JSONファイル -> Data型 への変換
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
// Data型 -> Codable型に準拠した構造体 への変換(JSON解析)
// -> <返却型>リストの各プロパティに一対一で対応するJSONデータが代入
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
表示デバイスに応じた動的プレビュー
画面サイズの異なるデバイスに応じた動的プレビューを行う場合、ビューを描画するループ処理を行うForEach構造体と、
キーパス(key path)の基点となるインスタンス自身を表すアイデンティティキーパス(\.self)を用いて、デバイス毎にビューを生成する。
サンプルコード
struct LandmarkList: View { ... }
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
// デバイスに応じた動的プレビュー
// -> Identifiableに準拠していないリストを走査する場合は、キーパス式で記述
// <= リスト・セット・辞書などのコレクションを走査する場合、各要素は"id"プロパティをもつ必要がある
// Identifiableに準拠しない場合は、アイデンティティキーパス(\.self)を用いてidを指定
ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in
LandmarkList()
// プレビューするデバイスの指定
.previewDevice(PreviewDevice(rawValue: deviceName))
// プレビュー名の指定
.previewDisplayName(deviceName)
}
}
}
Handling User Input
Combine
Combineは、値の変更を監視するPublisher、値を更新するOperator、更新後の値を取得するSubscriberを提供する
フレームワークであり、「値の変更」を非同期イベントとして監視し、値の変更に伴う更新処理を自動で行う。
値の変更を監視するデータには@Publishedプロパティラッパを付与し、
監視データを保持するクラスをfinalクラスとしてObservableObjectプロトコルに準拠させる。
プロパティラッパ(property wrapper)
プロパティラッパを用いることで、値型データとして扱われる構造体(ビュー)の内部でプロパティの値にアクセスすることができる。
SwiftUIで用意されているプロパティラッパは、以下の通り。
| プロパティラッパ | データ型 | 読み書き | 管理ビュー |
|---|---|---|---|
| プロパティ | 値型 | 読み込み | - |
| @State | 値型 | 読み書き | ビュー自身 |
| @AppStorage | 値型 | 読み書き | アプリケーション自身 |
| @SceneStorage | 値型 | 読み書き | シーン自身 |
| @Binding | 値型 | 読み書き | 外部ビュー |
| @StateObject | 参照型 | - | ビュー自身 |
| @ObservedObject | 参照型 | - | 親ビュー |
| @EnvironmentObject | 参照型 | - | ビュー自身 外部ビュー |
| @Environment | 値型・参照型(環境値) | 読み込み | ビュー自身 |

