はじめに
本記事は、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 | 値型・参照型(環境値) | 読み込み | ビュー自身 |