見出し画像

❹ Playgrounds App教材のすべて 後編

アプリが開発できる Playgrounds 4 にはたくさんの無料サンプル(App教材)があります。
でも順に制覇しようとすると落とし穴に!

説明が最小限なうえにシンプルな順に並んではいません
入門者を迷わせる説明の間違い‼️などがいくつかありました。
はじめてアプリを作る場合はこの記事を参考に、効率よくサンプルに取り組んでください。(この記事は ❸ Playgrounds App教材のすべて 前編 のつづきです)
【2022年10月11日 🟢 5.2 おすすめする順番 に最新情報を追加しました】
【2022年1月31日 App教材コード・説明の間違いや要注意点に‼️マークを追加しました。
「地震計」GaugeBackground の説明を追加を含め2000字以上加筆しました。】

前編と後編でそれぞれ 5 つの App教材を解説し、どの順序で取り組むのがおすすめかはこちらに書きます。

この記事は『Playgroundsでアプリデビュー』マガジンで読むことができます。

マガジンは複数の有料記事をリーズナブルに読めます。
『Playgroundsでアプリデビュー』マガジンで6つの有料記事が読めます。

Playgroundsでアプリデビュー』マガジンの構成 ➡️このnote
 ⓪ iPadでアプリを作ろう【無料記事】
 ❶ App Store からアプリをリリースする準備
 ❷ PlaygroundsでApp教材を動かす
 ❸ Playgrounds App教材のすべて前編
➡️❹ Playgrounds App教材のすべて後編
 ❺ Playgournds でアプリの作り方
 ❻ Playgrounds で作ったアプリを公開する




🟢 前編で解説した App教材

🟢 3.1 プロフィール🟢 3.2 ミームメーカー🟢 3.3 自分だけの物語を選択する🟢 3.4 水準器🟢 3.5 予定表 は ❸ Playgrounds App教材のすべて 前編 で解説しています。

章の番号は前編からつづきます。




型やメソッドなどに英文ドキュメントへのリンクを付けています。
リンクのある型やメソッドは SwiftUI などフレームワークのものです。
リンクのないものは基本的に App プロジェクト内で定義されているものです。

ドキュメントは英文なので、最初はとてもむずかしく感じると思います。
でも形式が決まっていますし、翻訳ツールを使うこともできます。
できるだけドキュメントを見る癖を付け Swift と SwiftUI のスキルを獲得してください。


🟢 3.6 地震計

サブタイトルが「モーション・センサー・データを可視化する」です。(英語タイトルは「Seismometer」)

地震計 App教材


3.6.1 概要

iPad のモーションセンサーを使い、振動情報を針と折れ線グラフの二つの形式で表示します。
CoreMotionフレームワークを使い iOS のセンサーからデータを読み取る方法のサンプルです。
変化するデータに常に反応できる SwiftUI ビューの構築と、シンプルな図形でカスタム画面を表示する方法を学びます。
見た目を変更したり、感度を変えたりするなどカスタマイズできます。

プロジェクトの容量は 3.6 MBです。
CoreMotion の扱いは「水準器」と同じですが、まったく別のアプリになっています。
水準器」の次にこの「地震計」に取り組むのがおすすめです。


3.6.2 内容

コードのファイル数は 8 です。
画像はありません。
MotionDetector.swift と DoubleExtension.swift は「水準器」と同じです。

まずタスクで説明のないコードの概要を書きます。


SeismometerApp

SeismometerApp はタスクには説明がありません。
SwiftUI アプリの標準的なコードです。
SeismometerBrowser ビューをトップレベルで表示します。
オブザーバブルオブジェクトはありません。


LineGraph

LineGraph はタスクには説明がありません。
地震波形のグラフを表示するビューです。
LineGraph ビューにはプレビュー表示のコードがないため単独でプレビューは表示しません。

func yGraphPosition(_ dataItem: Double, in size: CGSize) -> Double {
波形グラフの Y 座標を計算するメソッドです。
func xGraphPosition(_ index: Int, in size: CGSize) -> Double {
波形グラフの X 座標を計算するメソッドです。

グラフは独自の表示が可能なビュー Canvas を使って描いています。

init(opaque:colorMode:rendersAsynchronously:renderer:) Canvas 型イニシャライザー
Canvas { context, size in は renderer 引数をトレイリングクロージャーで記述しています。
opaque:、colorMode:、rendersAsynchronously: の引数は省略されています。
クロージャーの引数は (inout GraphicsContext, CGSize) の二つです。
GraphicsContext が描画環境で、CGSize は描画範囲です。
GraphicsContext 型は SwiftUI フレームワークです。

context.stroke(lines, with: .color(.black.opacity(0.25))) で目盛り線を描画し
context.stroke(path, with: .color(.accentColor)) でグラフを描画しています。
stroke(_:with:lineWidth:) で指定された線幅でコンテキストにパスを描画します。
Path 型を使っています。
Path について詳しくは「SwiftUI初級脱出パッケージ」マガジンで読める「SwiftUIのPath図形表示」を参照してください。

onChange(of:perform:)

.onChange(of: data) { _ in
   timestep += 1
}

data プロパティが更新されたら timestep をプラス1します。
タイマーのインターバルで data が定期的に更新されるので、その結果このビューは毎回全部描き直しています。


GaugeBackground

GaugeBackground  はタスクに説明がありません。
Path で半円を描き ForEach で目盛り線を描いています。
プレビューで確認してください。

Angle 型配列を ForEach で扱うために、ファイルの冒頭で Angle 型を Identifiable にしています。

Angle型については「SwiftUIのPath図形表示」の「5-1 Angle型」、半円の角度指定については「5-2 角度の起点と表示の注意」を参照してください。


3.6.3 タスク:モーションディテクタ

このタスクで説明するのは MotionDetector.swift です。
このファイルは「水準器」とほぼ同じです。
❸ Playgrounds App教材のすべて 前編」の「3.4.3  タスク:モーションディテクタ」を参照してください。
‼️地震計」では pitch と roll の値は使いません。

コードを共用しているため、「地震計」アプリでは不要なコードもあります。

zAcceleration は z軸の加速度です。
型は Double で単位は G(重力加速度)です。(0.01なら 0.01G)

updateMotionData() メソッドがタイマーにより繰り返し実行されます。


3.6.4 タスク:針地震計

このタスクで説明するのは NeedleSeismometer.swift です。
プレビューでもリアルタイムで振動に対応した針の動きを表示します。

ステップ 3/9
VStack で三つのビューを Spacer() ではさんでレイアウトしています。

ステップ 4/9
ZStack で針地震計部分を表示しています。
GaugeBackground(width: 250) は背景の目盛り付き半円を表示します。
プレビューは下のページ切り替えで NeedleSeismometer.swift のプレビューに切り替えることができます。

編集中のコードのプレビューに切り替える

ステップ 5/9
針の表示や傾き(回転)はタスクの説明で確認してください。
accentColor はApp設定で選択した「アクセントカラー」に対応します。

アクセントカラーの確認と変更

ステップ 6/9
rotationEffect(_:anchor:)  回転中心を指定して回転します。
このコードでは回転角度 rotationAngle が常に変化するので針が振動しているように表示します。

TextSpacer() ではさんでいます。
VStack 内のすべての Spacer ビューは同じ高さ(HStack 内なら同じ
幅)になります。

プレビューとコードの対応

ステップ 8/9
UnitPoint は相対的な位置を示します。
let needleAnchor = UnitPoint(x: 0.5, y: 1) は x方向の中央、y 方向の最大値(針の端)を意味します。

ステップ 9/9
overlay(alignment:content:) でビューの上にビューを重ねます。
このコードでは重ねるビューが VStack 内の Spacer と Circle ですが、Spacer は透明なので位置調整の働きのみです。


3.6.5 タスク:グラフ地震計

このタスクで説明するのは GraphSeismometer.swift です。
プレビューでもリアルタイムで振動のグラフを表示します。

ステップ 8/16
グラフを表示するのは LineGraph ビューですが、説明はありません。
横スクロールしつづけるしくみをぜひ解読してみてください。

ステップ 9/16
clipped(antialiased:) は LineGraph をこのビュー( GraphSeismometer )の境界内だけに表示します。
(地震波形が上下にはみ出す可能性があるためです)

ステップ 10/16
cornerRadius(_:antialiased:) で LineGraph を角丸に表示します。

ステップ 11/16
aspectRatio(_:contentMode:) アスペクト比を 1 に指定し正方形に表示します。

GraphSeismometer のプレビュー

ステップ 12/16
スライダーの調整で波形の振れ幅が変わります。
Slider 型イニシャライザーでここで利用している ‼️ 最後に label を指定する init(value:in:onEditingChanged:minimumValueLabel:maximumValueLabel:label:) は iOS 15.4 で Deprecated です。

ステップ 14/16
onUpdate クロージャーの内容を .onAppear で設定しています。
水準器」では onUpdate は未使用でしたが、「地震計」では使っています。

ステップ 15/16
onUpdate クロージャーは、data 配列の最後に -zAcceleration を追加しています。
onUpdate クロージャーは、MotionDetector の updateMotionData() で実行されます。
updateMotionData() はタイマーにより繰り返し実行されるので data 配列は繰り返し更新されます。
zAccelerationの値を配列に追加しているので、値に変化がなくても配列は変更となりグラフは再表示されます

ステップ 16/16
配列の件数が maxData を超えた場合は配列の先頭から1件取り除きます。
dropFirst(_:) は Array のメソッドです。


3.6.6 タスク:地震計ブラウザ

このタスクで説明するのは SeismometerBrowser.swift です。

ステップ 2/4
@StateObject private var detector = MotionDetector(updateInterval: 0.01)
の部分でタイマーのインターバルを 0.01 秒で初期化しています。

表示は List を使っています。

SeismometerBrowser コードとプレビューの対応

Graph のイメージは
Image(systemName: "waveform.path.ecg.rectangle")
で表示しています。
Image 型イニシャライザー init(systemName:) 引数 systemName: のイニシャライザーは SF Symbols の名称で指定します。
Playgrounds ver4 では「ライブラリから追加」ボタン(ボタン)で「waveform」で検索すると確認できます。


3.6.7 タスク:Doubleの拡張

このタスクで説明するのは DoubleExtension.swift です。
このコードは「水準器」と同じです。
❸ Playgrounds App教材のすべて 前編」の「3.4.6  タスク:Doubleの拡張」を参照してください。


3.6.8 「地震計」を実行

App名は「Seismometer」です。
実行するとブラウザー画面を表示します。

地震計実行画面

針地震計を選ぶと画面が切り替わります。

Needleに切り替え

地震計ブラウザーに戻り、グラフ地震計を選ぶと画面が切り替わります。
スライダーで感度を変更できます。
グラフは毎回すべて再表示するので、すべての波形の振幅が同時にかわります。

Graph に切り替え


🟢 3.7 イメージギャラリー

サブタイトルは「写真を操作する」です。(英語タイトルは「Image Gallery」)

イメージギャラリー App教材


3.7.1 概要

写真を読み込みグリッドで表示します。
写真の追加と削除、グリッドのカラム数を調整できます。
非同期コードを使って写真を読み込む方法を学びます。
オブザーバブル・データ・モデルの使い方を学びます。

プロジェクトの容量は 19.6 MBです。


3.7.2 内容

コードのファイル数は 9、画像ファイルは22 です。
グリッドを応用しているので「🟢 4.1 グリッドを使った整理」と「🟢 4.2 グリッドの編集の後に取り組むべき教材です。

グリッドについて詳しくは「SwiftUI初級脱出パッケージ」マガジンで読める「Lazy な Stack と Grid」を参照してください。

色やシンボルは表示に時間はかかりません。
画像の表示やダウンロードは時間がかかる場合があり、このApp教材はその対策部分に重点を置き説明されています。

一方まったく説明のないファイルも複数あります。
このプロジェクトの 9つのソースコードのうち、4つの説明がありません。
まずタスクで説明のないコードの概要を書きます。


ColumnStepper

ColumnStepper の説明はガイドにありません。
ColumnStepper はビューで body には Stepper(title, value: $numColumns, in: range, step: 1) { があり、値が変更されるとアニメーションありで GridItem の配列 columns を返します。

columns は ColumnStepper のプロパティでバインディングなので値を返すことができます。


FileManagerExtensions

FileManagerExtensions の説明がガイドにありません。
FileManagerExtensions は FileManager を拡張(独自の便利な機能を追加)しています。
このファイルは英文のコメントがあります。

追加するファイルをドキュメントディレクトリーにコピーしたり、ドキュメントディレクトリーのファイルを削除したりします。
内容は FileManager の知識が必要です、じっくり取り組むと後々役に立ちます。
追加しているのは次のプロパティとメソッドです。

var documentDirectory: URL?
func copyItemToDocumentDirectory(from sourceURL: URL) -> URL?
func removeItemFromDocumentDirectory(url: URL)
func getContentsOfDirectory(_ url: URL) -> [URL]

documentDirectory プロパティーの
return
self.urls(for: .documentDirectory, in: .userDomainMask).first は
urls(for:in:) メソッドでドキュメントディレクトリーを返します。
.documentDirectory は iOS でアプリが利用可能なディレクトリーのひとつです。
.userDomainMask  は iOS でアプリが利用者データを保存する場所を指定します。

copyItemToDocumentDirectory(from:) メソッドの引数は URL 型です。
lastPathComponent はURLの最後のパスコンポーネント(通常はファイル名)を返すプロパティーです。
appendingPathComponent(_:) は指定されたパスコンポーネントを追加した新たな URL を返します。

FileManager 型のメソッドやプロパティ
fileExists(atPath:) は指定パスのファイルまたはディレクトリが存在するかを返します。
copyItem(at:to:) は指定されたURLのファイルを同期的に新しい場所にコピーします。
removeItem(at:) は指定されたURLのファイルまたはディレクトリを削除します。
FileManager 型 の default プロパティはプロセスの共有ファイルマネージャーオブジェクト(インスタンス)を返します。
fileExists(atPath:isDirectory:) は指定パスのファイルまたはディレクトリが存在するかを返し、そのパスがディレクトリかどうかを確認できます。

getContentsOfDirectory(_:) の
contentsOfDirectory(at:includingPropertiesForKeys:options:) は指定されたディレクトリの浅い検索を実行し、含まれているアイテムのURLを返します。


DetailView

DetailView の説明がガイドにありません。
AsyncImage ビューで url データから画像を表示します。
画像データダウンロード中は ProgressView を表示します。

‼️ プレビューのコードで指定している "mushy1" がプロジェクトにないファイル名なので、プレビューを表示しません
"bobcat" のようにプロジェクトに内蔵している画像ファイル名にするとプレビューを表示します


PhotoPicker

PhotoPicker の説明がガイドにありません。
このコードは行数も多く難しいです。
インポートしているフレームワークは PhotosUI ですが、ドキュメントではタイトルが PhotoKit で Photos と PhotosUI フレームワークが含まれます。

PhotoPicker のコードは PhotoKit の PHPickerViewController を表示するために必要です。
PHPickerViewController は UIKit のビューコントローラーです(UIViewController を継承しています)。

SwiftUI では UIKit のビューコントローラーを表示するしくみを持っています。
UIViewControllerRepresentable プロトコルは UIKit の UIViewController を継承したクラスを SwiftUI で表示します。

PhotoPicker のコードには英文のコメントがありますが理解するには UIKit の知識が必要です。
写真の選択に PHPickerViewController 使っています。

ピッカービューで選択した写真はピッカービューのデリゲートで dataModel に追加しています。
このため PhotoPicker には引数はありません。
デリゲートには Coordinator クラスを使って対応しています。

デリゲートは UIKit の重要なしくみです。
SwiftUIでドラッグ&ドロップ」などを参照してください。

PHPickerConfiguration ピッカービューコントローラーの設定方法に関する情報を含むオブジェクトの型です。
PHPickerResult はユーザーのフォトライブラリから選択したアセットを表すタイプです。
loadFileRepresentation(forTypeIdentifier:completionHandler:) で選択した画像の URL 情報を得てクロージャー内でドキュメントディレクトリにコピーしています。


3.7.3 タスク:Appデータの共有

このタスクで説明するのは ImageGalleryApp.swift です。
オブザーバブルオブジェクトであるDataModel 型のインスタンス dataModel を environmentObject(_:) モディファイアー(修飾子)を使いすべてのビューで利用可能にしています。

最上位ビューは NavigationView その中に GridView を表示します。
navigationViewStyle(_:)  stack は NavigationViewStyle のひとつです。


3.7.4 タスク:項目をモデリングする

このタスクで説明するのは Item.swift です。

ForEach には ForEach(story[pageIndex].choices, id: \Choice.text) のように id 引数で識別子を指定する使い方のほか、ここで使っている Identifiable プロトコルに準拠し識別子プロパティを用意する方法もあります。

ForEach 型
init(_:content:) id 指定なしイニシャライザー
init(_:id:content:) id 指定ありイニシャライザー



タスクのステップ 1/3 の説明で『これを容易にするため、識別可能に準拠するモデルを...』とあります。

ステップ 1/3

‼️ この「識別可能」は Identifiable のことでドキュメントにリンクしています。
Identifiableプロトコル名なので、ここは翻訳しすぎと思います。



Identifiable プロトコルはプロパティ id が利用可能なことを保証します。
このプロトコルは SwiftUI ではなく Swift Standard Library にあります。
Identifiable については「SwiftUI初級脱出パッケージ」にある「SwiftUI でリスト表示」を参照してください。

ステップ 2/3
let id = UUID()
UUID Foundation フレームワークの型でアイテムを識別するために使用できる普遍的に一意の値です。

UUID() は毎回識別可能な値を返します。
ここで初期値を UUID() にしているため、デフォルトのイニシャライザーでは id プロパティの設定は不要になり Item(url: ...) のように利用可能です。

ステップ 3/3
URL は Foundation フレームワークの型です。


3.7.5 タスク:データモデル:イメージURLを収集する

このタスクで説明するのは DataModel.swift です。

ステップ 1/6
このタスクの説明でいきなり「バンドル」が出てきます。
「バンドル」はアプリの構造と関係します。
バンドルはフォルダー(ディレクトリ)の階層構造を持ち、任意のファイルを含むことができます。
アプリのバンドル内には複数のファイルを含んでいますが、ひとつのアプリとして扱うことができます。
アプリのバンドルを「アプリケーションバンドル」と呼ぶことがあります。

Appプロジェクトに追加した画像ファイルはアプリをビルドすると、アプリのバンドル内にコピーされます。
内蔵したい画像はプロジェクトに追加してください。(その容量だけアプリサイズが大きくなります)

ステップ 2/6
ObservableObject や @Published は「SwiftUI初級脱出パッケージ」で読める「SwiftUIのデータフローその2」を参照してください。

ステップ 3/6
FileManager はファイルの読み書き機能を提供するクラスで Foundation フレームワークにあります。
FileManager.default.documentDirectory は FileManagerExtensions にあるアプリのドキュメントディレクトリを返すプロパティです。

filter(_:) は Array 型のメソッドです。

ステップ 4/6
Bundle 型は「バンドル」を扱うためのクラスです。
.main プロパティで「アプリケーションバンドル」を指定します。
アプリケーションバンドル」の画像などをリソースと呼びます。
.urls(forResourcesWithExtension:subdirectory:) メソッドで「アプリケーションバンドル」内の指定拡張子のファイルの URL データ配列を得ています。

ステップ 5/6
insert(_:at:) は Array 型のメソッドです。
ほかに append(_:) や remove(at:) メソッドがあります。

補足
extension URL { は isImage プロパティを追加します。
isImage は拡張子が jpg など画像のものであれば true を返します
extension はこのように独立したファイルでなくても利用できる Swift の機能です。
pathExtension はファイルの拡張子文字列です。
contains(_:) はArray型のメソッドです。


3.7.6 タスク:グリッド項目を作成する

このタスクで説明するのは GridItemView.swift です。

ステップ 4/7
ZStack を使っています。
if let url = item.url { で item.url が値なしでなければ AsyncImage を表示します。
url が値なしならば何も表示しません
init(alignment:content:)  ZStack型のイニシャライザー

ステップ 5/7
AsyncImage(url: url) { で画像を表示します。
AsyncImage はiOS 15以降で利用可能なビューです。
イメージを非同期で読み込みます。
読み込み完了するまでは ProgressView() を表示するコードです。

ステップ 7/7
ProgressView 型のイニシャライザー init() はラベルなしで総量が不明の不確定な進捗状況を表示します。

‼️ なお GridItemView_Previews の "mushy1" はプロジェクトにない画像ファイル名なのでプレビューは表示できません
プロジェクトに存在する画像ファイル名にするとプレビューを表示できます。


3.7.7 タスク:ギャラリーを作成する

このタスクで説明するのは GridView.swift です。

ステップ 1/8
‼️ 『"カラーグリッド" を確認してください。』とあります。
"カラーグリッド"は「グリッドを使った整理」のことと思われます。
グリッドを使った整理」だけでなく「グリッドの編集」も理解してから取り組んでください。

カラム数の切り替えはステッパーで行いますが、独立したビュー ColumnStepper で行うため少し分かりにくくなっています。

GridView の gridColumns は
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
と定数 initialColumns で起動時のカラムのみ指定しています。
ColumnStepper で値を変更した後はバインディングにより gridColumns は更新され、再表示されます。

ステップ 6/8
GeometryReaderLazyVGrid のひとつのグリッドのサイズ情報を geo に設定し、GridItemView の引数に渡しています。

NavigationLink のリンク先 DetailView の説明はガイドにありません。AsyncImage(url: item.url) を表示するシンプルなビューです。


3.7.8 タスク:ギャラリーにイメージを追加する

このタスクで説明するのは GridView.swift です。

ステップ 1/6
‼️ 「ツールバーの”2追加”ボタン(+)を押すたびに、この値がtrueに切り替わります。」の「2」はタイプミスで不要のようです。

編集モードの動作は「🟢 4.2 グリッドの編集」を参照してください。

追加は PhotoPicker() で処理します。
(PhotoPicker はタスクには説明はありません)


3.7.9 「イメージギャラリー」を実行

App名は「Image Gallery」です。
起動すると内蔵している写真をグリッドで表示します。

イメージギャラリーの実行画面

Edit をタップすると編集モードで削除ボタンとカラム数ステッパーを表示します。
カラム数を自由に増減できます。

編集中

編集モードを終え、写真をタップすると拡大します。

写真を選択

追加ボタンをタップすると PhotoPicker を表示します。
PhotoPicker はデバイスにある写真やアルバムから画像を選びグリッドに追加することができます。

追加ボタンで PhotoPicker を表示


🟩 4 Appを拡張する

「拡張」のタイトルから基礎を終えた後の教材のように感じますが、実際にはベーシックな内容です。
むしろ「Appの作成を始めよう」を終えた直後に取り組むべき教材です。


🟢 4.1 グリッドを使った整理

サブタイトルは「グリッドの基本を学ぶ」です。(英語タイトルは「Organizing with Grids」)

グリッドを使った整理 App教材


4.1.1 概要

コンテンツをグリッド形式で配置する方法を学びます。
ScrollView を使ったスクロールの方法、LazyVGrid を使ったグリッドの作成方法、ForEach を使った配列の繰り返し処理、ボタンを使った操作などを学びます。
状態を使って Text ビューの色をアップデートする方法を学びます。

プロジェクトの容量は 3.3 MBです。
最もシンプルな App教材です。


4.1.2 内容

コードのファイル数は 2、画像はありません。

一つはApp のコードなので、このアプリは基本的に ContentView のみのとてもシンプルな教材です。

グリッドは画面サイズの違いや画面の回転により柔軟にレイアウトが可能なビューです。
詳しくは「SwiftUI初級脱出パッケージ」マガジンで読める「Lazy な Stack と Grid」を参照してください。


4.1.3 タスク:Appの構成

このタスクで説明するのは ColorGridApp.swift です。

アプリのコードの説明からはじまります。

ステップ 1/4
ただしファイル名が「ColorGridApp.swift」で型名が「SimpleGridApp」となっていて混乱します
Xcode 用のチュートリアルでは型名が「ColorGridApp」になっています。

ステップ 2/4
@main はアプリの実行開始の目印となる Swift 言語の属性です。

ステップ 3/4
App プロトコルの body は型が「some Scene」です。
Scene(シーン)はViewを表示します。
ここでは ContentView を表示します。
ContentView() は ContentView 型のイニシャライザーです。

App プロトコルや Scene 型は SwiftUI フレームワークです。
App
Scene
WindowGroup


4.1.4 タスク:グリッドの作成

このタスクで説明するのは ContentView.swift です。

ステップ 1/10
struct ContentView: View { の「ContentView」は型の名称です。
別の名称でもかまいませんが、ColorGridApp.swift で参照しているので同じ名前にしなければいけません。
View プロトコル

ステップ 2/10
allColors は Color 型の配列で、リテラルで初期化しています。
通常の Swift 言語のコードです。

ステップ 3/10
body プロパティにはひとつの VStack があります。
VStack の中身は TextScrollView です。

ステップ 5/10
ScrollView は LazyVGrid をスクロール可能な状態で表示します。

ContentView とプレビュー表示

ステップ 6/10
LazyVGrid については「Lazy な Stack と Grid」を参照してください。

ステップ 7/10
let columnLayout = Array(repeating: GridItem(), count: 3)

let columnLayout = [ GridItem(), GridItem(), GridItem() ]
と同じです。
カラム数が3 固定で画面幅によりサイズが変わるグリッドを表示する指定です。
この指定では画面を回転してもカラム数は変わらずボタンのサイズが変わります。
Array 型の init(repeating:count:) イニシャライザーです。

ステップ 8/10
ForEach(allColors, id: \.description) { color in  は配列 allColors を順に処理します。
配列から取り出した値が color に入っていてクロージャの中で利用可能です(color はクロージャーの引数です)。
ForEach 型のイニシャライザー init(_:id:content:) の id 引数は allColors を識別するための指定です。
ここでは allColors の配列に同じ色がないので、色自身を識別子にしています。
id: \.description の書き方で「\」(バックスラッシュ)で始まる部分は、Swift の用語で「キーパス」です。
Color 型インスタンスの description プロパティを識別子として指定しています。

JISキーボードの場合バックスラッシュoption を押しながら円記号¥で入力します。

ステップ 9/10
Button も SwiftUI のビューです。
init(action:label:) イニシャライザーの action引数で selectedColor を自分の色に置き換えるコードです。

ステップ 10/10
label: はボタンの表示内容を決めるコードです。

Button {
    selectedColor = color
} label: {
    RoundedRectangle(cornerRadius: 4.0)
        .aspectRatio(1.0, contentMode: ContentMode.fit)
        .foregroundColor(color)
}

この書き方もトレイリングクロージャーです。
ラベルには文字だけではなく RoundedRectangle のようなシェイプなど SwiftUI のビューならなんでも表示できます。
色を color にした角丸長方形をアスペクト比 1.0 で表示します。

ステップ 10/10
init(cornerRadius:style:) は RoundedRectangle 型のイニシャライザーです。
foregroundColor(_:) で前景色を指定しています。

続きをみるには

残り 13,390字 / 26画像
この記事のみ ¥ 500

今後も記事を増やすつもりです。 サポートしていただけると大変はげみになります。