状態変化を監視する変数は body View の上で宣言する
body内で宣言すると状態変化しない(状態変化する時にViewを表示し直すため)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct test: View { @State var aaa = 0 //OK var body: some View { @State var bbb = 0 //NG Button(action: { aaa += 1 bbb += 1 }) { Text("\(String(aaa))") } Text("\(String(bbb)") } } |
.fullScreenCover()等のシート修飾子の位置
SwiftUIの.sheet(), .fullScreenCover(), .popover() などのシート修飾子は、原則としてトリガーとなるView(ここではButton)と同じ階層か、その親Viewに適用するのが一般的です。
シート修飾子を**Buttonの直下に適用するのは良い方法**です。これにより、どのボタンがどのシートを表示するのかがコード上で明確になります。
SwiftUIの.fullScreenCover()などのシート修飾子の配置は、慣れるまで混乱しやすいポイントの一つです。
「表示のトリガーとなるビュー(Button)に直接つける」、あるいは**「そのビューを囲むコンテナ(VStackなど)の直後につける」**と覚えておくと、今後もスムーズに開発を進められると思います。
適用場所のベストプラクティス
基本的なルールは、シートの表示を制御する状態変数 (@State var isShowingSheet: Bool) が定義されているViewの直下(そのbodyのトップレベルの子Viewの直後)に修飾子を付けるのが最も見通しが良く、推奨されます。
1. Button直下に適用する例(推奨される方法)
トリガーとなるButtonに直接修飾子を適用します。これが一番シンプルでわかりやすいパターンです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
struct ContentView: View { @State private var isShowingCover = false var body: some View { VStack { Text("メインコンテンツ") // ✅ Buttonに直接 .fullScreenCover() を適用 (OK) Button("フルスクリーン表示") { isShowingCover = true } .fullScreenCover(isPresented: $isShowingCover) { FullScreenView() } // 他のViewがあれば続く Spacer() } } } |
2. コンテナView(VStack, ZStackなど)の直下に適用する例
ボタンがコンテナ内にあり、修飾子をコンテナ全体に適用しても動作します。
⚠️ 避けるべき場所
修飾子をVStackやHStackなどのコンテナのコンテンツ内(VStack { ... } の中括弧内)に適用しようとすると、複数のViewに適用しようとしていると見なされてエラーになるため注意してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct ContentView: View { @State private var isShowingCover = false var body: some View { VStack { Text("メインコンテンツ") Button("フルスクリーン表示") { isShowingCover = true } // ❌ ここに .fullScreenCover() を適用するとエラーになる // .fullScreenCover(isPresented: $isShowingCover) { ... } } } } |
まとめ
階層が深くなっても、Buttonと対応する@State変数が存在するViewで、トリガーとなるView(Button)の直後に修飾子を付けるのが、最もシンプルで推奨される方法です。迷ったら「表示させるボタンの直後」と覚えておくと良いでしょう。👍
id: \.self
ForEachでコレクションを一つずつ取り出して処理する時にviewのidに第一引数と同じものを与える?
このForEachの\.selfで動的に複数のListViewを作った場合挙動がおかしくなる。その場合はUUIDで構成したIDを割り当てる。
参考記事: https://zenn.dev/kntk/articles/1f1b40da6fe181
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
let aaa: [String] = ["a", "b", "c"] ForEach(aaa, id: \.self){ bbb in Text("\(bbb)") } //テキストが a b c で作成されると同時にtextのidもa b cに振られる? //動的にList等のViewを作成する時は下のようなUUID構造体でIDに割り振らないと動作がおかしくなる struct aaa: Identifiable{ var id = UUID() //変数名は id のみ有効 var bbb: String } struct ContentView: View{ @State var ccc: [aaa] = [aaa(bbb: "あああ"), aaa(bbb: "いいい"), bbb(ccc: "ううう")] var body: some View { List{ ForEach (ccc) { a in Button(action: { }){ Text(a.bbb) } } } } } |
Data型
Swift の Data は
- バイト列(UInt8 の配列)
- つまり
[UInt8]のようなもの
例)
|
1 |
var d = Data([0x00, 0xFF, 0x12]) |
添え字アクセスも 1バイトだけ に使える:
|
1 |
d[0] = 0x10 // OK(1バイトの数値) |
つまり Data の添え字には
入れられるのは「0〜255 の UInt8 だけ」
構造体、クラスの違い
構造体はインスタンス化せず使える値型
クラスはインスタンス化しないと使えない参照型
クラス、構造体の使い分け
🔹クラス vs 構造体(API取得コードの場合)
| 用途 | 向いている | 理由 |
|---|---|---|
| API通信やネットワーク管理 | ✅ クラス(class) | 状態を保持(例: セッション、リトライ回数、認証トークンなど)しやすい。参照型なので複数箇所で共有できる。 |
| データモデル(受信したJSON構造など) | ✅ 構造体(struct) | 値型なので安全で軽い。Codable対応もしやすく、スレッド間の共有にも安全。 |
- class:状態や継続的な管理(APIClient、TokenManagerなど)
- struct:一時的なデータやJSONモデル
- enum:APIの種類・エラー種別の定義などに便利
クラスの初期化順メモ
viewのカスタマイズは三項演算子で行う
SwiftUIのif文はオブジェクトを一度インスタンス化してから分けるのでその分処理が無駄になる
プロトコルとは
C++とかの抽象クラス的な機能
待機処理
Task{}を使いTask.sleep()
|
1 2 3 4 5 6 |
Button("1秒後に実行") { Task { try? await Task.sleep(for: .seconds(1)) print("1秒後に実行された!") } } |
❌ やってはいけない方法
sleep(1)
→ UIフリーズしてアプリ固まる
DispatchQueue.main.asyncAfter(…)
→ Swift Concurrency 時代だとあまり推奨されず
→ 同期保証が弱くなりバグることがある
(ただし完全にNGではなく、従来コード互換で動く)
非同期
Task{} を作る → すぐにバックグラウンドで開始
関数で async にすればTask{}使わず await でおk
Listの背景色modifierについて
List内の背景色はすこし特殊で本体部と個別Viewを設置する【行】の背景色とで別れていて記述も通常と少し違う
文字色は通常通りの設定で可能
|
1 2 3 4 5 6 |
List { Text("あああ") .listRowBackground(Color.blue) //Textの行の背景色 } .scrollContentBackground(.hidden) //一度本体部を透明にする .background(Color.gray) //透明にしてから色を設定する |

Viewに変数を渡した時の#Previewの書き方について
初期化時にプロパティを渡す子ビューのプレビューを作成するには、#Previewマクロの中で、そのビューのイニシャライザ(init)に必要な引数を指定します。
基本的な書き方
たとえば、TestViewがmessage: Stringというプロパティを持っている場合、プレビューは以下のように書きます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import SwiftUI struct TestView: View { let message: String // 初期化時に必須のプロパティ var body: some View { Text(message) } } #Preview { // プレビューの中で、TestViewの初期化子に必要な引数を指定する TestView(message: "プレビュー用のメッセージ") } |
#Previewマクロのブロックの中は、ビューを返すクロージャとして扱われます。このため、TestView()のように引数を省略して呼び出すことはできず、TestView(message: "...")のように正しい引数を渡す必要があります。
複数のプレビューを作成する場合
さまざまな状態をテストするために、複数のプレビューを定義することもできます。
|
1 2 3 4 5 6 7 |
#Preview("通常") { TestView(message: "通常メッセージ") } #Preview("長いメッセージ") { TestView(message: "これは非常に長いメッセージです。テキストの折り返しを確認します。") } |
プレビューでダミーデータを使用する場合
プレビュー専用のダミーデータ(MockDataなど)を作成しておくと、より柔軟なプレビューが可能になります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import SwiftUI struct User { var name: String } struct UserView: View { let user: User var body: some View { VStack(alignment: .leading) { Text("ユーザー名:") Text(user.name).font(.headline) } } } // プレビュー用のダミーデータ let mockUser = User(name: "テストユーザー") #Preview { UserView(user: mockUser) } |
つまりプレビュー用に変数名の右は何でも良いから書いておけばおk
if文の後に【 , 】で && の代わりになる
Swift特有の文法 guard let でよく使われるらしい。
|
1 2 3 4 5 6 7 |
if let data = try KeychainHelper.read(service: service, account: account), let saved = try? JSONDecoder().decode(SavedToken.self, from: data), saved.expiry > Date() { self.token = saved.token self.expiry = saved.expiry return saved.token } |
修飾子の順序
推奨される順序
|
1 2 3 4 5 6 7 |
Button("アクション") { // アクション } .keyboardShortcut("k", modifiers: .command) // ← 最初に配置 .buttonStyle(.borderedProminent) .scaleEffect(clear_flg ? 0.95 : 1.0) .animation(.linear, value: clear_flg) |
理由
SwiftUIの修飾子は機能的なもの → 見た目のものの順序が推奨されます:
1. 機能的な修飾子(最初)
.keyboardShortcut()– 動作・機能.disabled()– 状態.help()– ヘルプテキスト
2. スタイル修飾子(中間)
.buttonStyle()– 基本スタイル.controlSize()– サイズ
3. 視覚効果修飾子(最後)
.scaleEffect()– 変形.opacity()– 透明度.animation()– アニメーション
実践例
|
1 2 3 4 5 6 7 8 9 10 11 |
Button("保存") { save() } .keyboardShortcut("s", modifiers: .command) // 1. 機能 .disabled(isSaving) // 2. 状態 .help("ファイルを保存") // 3. ヘルプ .buttonStyle(.borderedProminent) // 4. スタイル .controlSize(.large) // 5. サイズ .scaleEffect(clear_flg ? 0.95 : 1.0) // 6. 視覚効果 .opacity(isSaving ? 0.5 : 1.0) // 7. 視覚効果 .animation(.linear, value: clear_flg) // 8. アニメーション |
なぜこの順序?
.keyboardShortcut()を最初に: ボタンの「本質的な機能」だから.buttonStyle()の前に: スタイルはボタンの見た目を変えるだけ.animation()を最後に: アニメーションは最終的な視覚効果
注意点
実際のところ、多くの場合は順序を変えても動作に問題はありません。ただし:
- 可読性: 一貫した順序の方がコードが読みやすい
- 予期しない動作: 一部の修飾子は順序に依存する場合がある
- チーム開発: 統一されたルールがあると保守しやすい
文字列をユニークに保持する:Set<String>()
var 変数 = Set<String>()は、「既に確認・処理した文字列のデータを一意(ユニーク)に保持する、変更可能な集合(セット)」を定義するコードです。
このコードの効果は、主に以下の3点に集約されます。
1. 重複しないデータの保持 (Setの特性)
- 効果:
Setは同じ要素を複数持てないため、文字列(String)が何度追加されても、自動的に1つだけ保持されます。 - 用途: 「どの画面を見たか」「どのデータを処理したか」などを管理する際に、同じ項目を複数登録するミスを防げます。
2. 高速な存在チェック (Performance)
- 効果:
seen.contains("item1")のように、特定の文字列が既にセットの中にあるかどうかを高速に検索できます。 - 用途: リスト表示で「このアイテムは表示済みか?」や、APIリクエストで「このデータは取得済みか?」を即座に判定できます。
3. 一時的な記録と再代入 (varの特性)
- 効果:
varで宣言されているため、処理の過程で新しい要素をseen.insert("item2")などで追加・変更できます。 - 用途: 画面遷移やスクロールに合わせて、動的に「seen(見たもの)」リストを更新していきます。
SwiftUIでの一般的な利用シーン
主に、特定の条件で一度だけ実行したい処理や、重複を除外したデータ管理に使われます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import SwiftUI struct ContentView: View { // 効果1, 3: 変更可能な、重複しない文字列のセット @State private var seen = Set<String>() let items = ["Apple", "Banana", "Apple", "Orange"] // 重複あり var body: some View { List(items, id: \.self) { item in HStack { Text(item) Spacer() // 効果2: 高速な存在チェック if seen.contains(item) { Image(systemName: "checkmark.circle.fill") } } .onAppear { // seenに要素を追加 seen.insert(item) } } } } |
注意点:SwiftUIでのデータ永続化
上記のコード単体では、Viewが再描画されるか画面を閉じるとseenの中身はクリアされます。アプリを閉じた後も記録を残したい場合は、@AppStorageやデータベース(CoreData等)を使用する必要があります。
スライス
SwiftUIやSwiftにおいて、配列に対する [1...] (1から始まる範囲指定)は「配列の2番目の要素から最後までの部分配列(サブシーケンス)」を抽出する効果があります。
具体的な効果と使い方
- 動作: 配列のインデックス(添え字)は0から始まるため、
[0]が1番目、[1]が2番目の要素となります。[1...]は添え字1以降(1, 2, 3, …)を切り出します。 - 用途: SwiftUIの
ListやForEachで「最初の要素(ヘッダー)を除外してリストを表示したい」場合などに非常に便利です。
サンプルコード
|
1 2 3 4 5 6 7 8 |
let array = ["A", "B", "C", "D", "E"] let subArray = array[1...] // subArray は ["B", "C", "D", "E"] となる (2番目以降) // SwiftUIでの利用例 List(Array(array[1...]), id: \.self) { item in Text(item) // B, C, D, E がリスト表示される } |
注意点
- ArraySliceの発生:
array[1...]はArray型ではなくArraySlice型(部分配列)を返します。後続の処理で配列として使いたい場合はArray(array[1...])のようにキャストする必要があります。 - エラーの回避: 配列が空、または要素が1つしかない場合、
array[1...]はエラー(Fatal error: Can’t form range with lowerBound > upperBound)になる可能性があります。要素数をチェックする(if array.count > 1)か、dropFirst()を使う方が安全です。 - 安全な代替手法:
dropFirst(1)も同じ「最初の1つを除く」効果があり、要素が少ない場合でも安全に動作するため、より安全なアプローチとして推奨されます
|
1 |
let safeSubArray = array.dropFirst(1) // ["B", "C", "D", "E"] (要素が少なくてもエラーにならない) |
.value.localizedCaseInsensitiveContains
文字列の検索において、大文字・小文字の区別をせず、かつ現在の地域設定(カルチャ)に依存した柔軟な部分一致検索を行うメソッドです。
検索機能やフィルタリング機能で「ユーザーが入力したキーワードを含む項目」を表示したい場合に非常によく使われます。
主な効果と特徴
- 大文字・小文字を区別しない (Case Insensitive)
- 地域設定を考慮する (Localized)
- 部分一致検索 (Contains)
SwiftUIでの具体的な使用例
リスト(List)内のデータを、検索フィールド(TextField)の入力値でフィルタリングする典型的な例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import SwiftUI struct ContentView: View { @State private var searchText = "" let items = ["Apple", "banana", "Orange", "Grape", "Pineapple"] // 検索テキストに基づいてフィルタリングされたリスト var filteredItems: [String] { if searchText.isEmpty { return items } else { return items.filter { $0.localizedCaseInsensitiveContains(searchText) } } } var body: some View { NavigationView { List(filteredItems, id: \.self) { item in Text(item) } .navigationTitle("Fruit Search") .searchable(text: $searchText, prompt: "果物を検索") } } } |
なぜ contains ではなく localizedCaseInsensitiveContainsか?
contains(単純一致): 大文字・小文字を厳密に区別するため、”apple” と検索して “Apple” はヒットしません。localizedCaseInsensitiveContains: 大文字・小文字、特殊文字の違いを無視して検索できるため、ユーザーフレンドリーな検索機能になります
注意点
文字列がオプショナル型(String?)の場合、安全にアンラップしてから使用するか、SwiftDataやCoreDataで利用する場合は、そのフレームワークの検索APIを使う方が適しています。
2次元配列.enumerated()
2次元配列に対して.enumerated()を使用する主な効果は、各要素の値(value)と、それに対応するインデックス(index: rowとcolumn)を同時に取得できることです。
これにより、ForEach内で単にデータを表示するだけでなく、位置情報に基づくUIのカスタマイズや、配列の直接更新が可能になります。
具体的な効果と使い方
2次元配列 [[T]] に対し、外側と内側の両方、あるいは片方で .enumerated() を使うことで、行と列の番号を把握しながらループ処理ができます。
1. インデックスを使った特定要素の特定と操作
.enumerated() は (index, element) のペアを返します。2次元配列では、外側(行)と内側(列)のインデックスを同時に取得して、グリッドレイアウトの作成や、特定のセル(row, column)をタップした際の挙動を指定できます。
|
1 2 3 4 5 6 7 8 9 10 |
// 例:2次元配列の表示 let gridData = [["A", "B"], ["C", "D"]] ForEach(Array(gridData.enumerated()), id: \.offset) { rowIndex, row in HStack { ForEach(Array(row.enumerated()), id: \.offset) { colIndex, cell in Text("\(cell) (\(rowIndex),\(colIndex))") // 値と位置(row, col)を表示 } } } |
2. 配列の直接更新 (Binding)
.enumerated() を使用して要素とインデックスを取得すると、$array[rowIndex][colIndex] のようにして、特定の要素をバインディング(双方向データフロー)として編集可能にできます。
3. ForEachでの使用と注意点
SwiftUIの ForEach に渡す場合、.enumerated() が返す型は直接IDとして使えないため、Array(配列.enumerated()) のようにラップして配列化するか、id: \.offset (インデックスをIDとする) を明示する必要があります。
enumerated() を使うメリット
- 位置依存のUI: グリッドやテーブルで、行/列番号に基づいて色や配置を変えたい場合に必須。
- データとビューの同期: 配列構造を保ったまま、インデックスに基づいてデータを更新するロジックがシンプルになる。
※注意点として、.enumerated() が返すのは配列の要素が減った場合でも、インデックスが連番(0, 1, 2…)で再採番されるため、要素固有のIDとして使うには注意が必要です。

コメント