ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-04-16中級

SwiftUI 開発で Antigravity が本当に役立つ場面と、そうでない場面

SwiftUI アプリ開発に Antigravity を取り入れる際の実践パターンを解説します。UI プロトタイピング・SwiftData モデル設計・テスト生成まで、個人開発者が日常的に使えるテクニックを紹介します。

swiftui2ios35app-dev47antigravity435xcode4swiftdata

SwiftUI でアプリを作っていると、実装の方針を決めるだけで30分が過ぎることがあります。「このアニメーション、.animation.withAnimation どちらで書くべきだろう」「SwiftData の @RelationshipdeleteRule、ここは .cascade にすべきか .nullify にすべきか」——そういった判断の積み重ねが、1日の終わりに妙な疲れを生むことはありませんか。

Antigravity をSwiftUI プロジェクトに組み込んでから、この種の「判断コスト」がかなり下がりました。ただ、どんな問いを持ち込んでも解決するわけではありません。効果が高い場面と、そうでない場面の差が意外とはっきりあります。ここでは個人で iOS アプリを開発している視点から、具体的なパターンをお伝えします。

プロジェクト設定ファイルに「制約」を先に書いておく

Antigravity が生成するコードの品質は、どれだけプロジェクトの文脈を伝えられているかに大きく左右されます。新しい iOS プロジェクトで最初にやっておきたいのが、.antigravity/settings.json へのプロジェクト方針の記述です。

{
  "instructions": "This is an iOS app using SwiftUI and SwiftData. Minimum deployment target is iOS 17. Use Swift 5.10 features where appropriate. Prefer @Observable macro over ObservableObject. Always add proper error handling with Result type or throws. Do not use deprecated APIs.",
  "exclude": [
    ".build/",
    "*.xcuserdata",
    "*.xcworkspace/xcuserdata"
  ]
}

@Observable マクロを優先する」「非推奨 API は使わない」この2行を書いておくだけで、古い ObservableObject パターンではなく iOS 17 以降のコードが出てくるようになります。毎回「iOS 17 の書き方で」と伝えなくて済むようになるのは、地味に効いてきます。

カスタムルールを活用する場合はAntigravity カスタムルール&プロジェクト設定 完全実践ガイドも参考になります。チーム開発ではなく個人開発でも、プロジェクトをまたいで設定を統一するのに使えます。

UI プロトタイピング:見た目より「制約」を先に伝える

SwiftUI の View 設計で Antigravity が最も力を発揮するのは、「どの修飾子の組み合わせが正解かわからない」という場面です。このとき、最終的な見た目をそのまま言葉で説明するより、制約条件と目的を先に提示する方が精度の高いコードが返ってきます。

// プロンプト例:
// 「カード型リストアイテムを作りたい。条件:
//  - タップ時にスケールアニメーション(バウンス感あり)
//  - SF Symbols アイコン + タイトル / サブタイトルの2行テキスト
//  - ダークモード対応(.secondary カラー使用)
//  - iOS 17 の @Observable モデルからデータを受け取る」
 
struct ItemCardView: View {
    let item: Item // @Observable Item モデル
    @State private var isPressed = false
 
    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: item.iconName)
                .font(.title2)
                .foregroundStyle(.accent)
                .frame(width: 44, height: 44)
                .background(
                    .accent.opacity(0.1),
                    in: RoundedRectangle(cornerRadius: 10)
                )
 
            VStack(alignment: .leading, spacing: 2) {
                Text(item.title)
                    .font(.headline)
                Text(item.subtitle)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }
            Spacer()
            Image(systemName: "chevron.right")
                .foregroundStyle(.tertiary)
        }
        .padding(16)
        .background(.background, in: RoundedRectangle(cornerRadius: 14))
        .scaleEffect(isPressed ? 0.97 : 1.0)
        .animation(.spring(duration: 0.2), value: isPressed)
        .onTapGesture {
            withAnimation { isPressed = true }
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
                isPressed = false
            }
        }
    }
}

このコードをそのまま使うより、「なぜ .spring(duration: 0.2) を使ったのか」「isPressed@State にした理由は」といった点を読み解くことに価値があります。生成されたコードが理解の素材になることで、次からは自分でアレンジできるようになります。

SwiftData モデル設計:関係性の設計ミスを防ぐ

SwiftData の @Relationship は、設計を間違えると後からスキーママイグレーションが必要になります。個人開発では特に厄介な問題で、リリース後のユーザーデータに影響することもあります。

Antigravity へのアプローチとして効果的なのは、ER 図ではなく日本語で関係性を説明する方法です。「プロジェクトを削除したらタスクも一緒に消す」「タグは複数タスクから参照できる多対多」と伝えると、deleteRule の使い分けを含めたコードが出てきます。

// プロンプト: 「タスク管理アプリ。プロジェクト削除→タスクも削除(cascade)。
// タグは複数タスクに紐づく多対多。Task は Codable も必要」
 
import SwiftData
 
@Model
final class Project {
    var name: String
    var createdAt: Date
 
    @Relationship(deleteRule: .cascade, inverse: \Task.project)
    var tasks: [Task] = []
 
    init(name: String) {
        self.name = name
        self.createdAt = .now
    }
}
 
@Model
final class Task: Codable {
    var title: String
    var isCompleted: Bool
    var dueDate: Date?
    var project: Project?
 
    @Relationship(deleteRule: .nullify, inverse: \Tag.tasks)
    var tags: [Tag] = []
 
    enum CodingKeys: String, CodingKey {
        case title, isCompleted, dueDate
    }
 
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        title = try c.decode(String.self, forKey: .title)
        isCompleted = try c.decode(Bool.self, forKey: .isCompleted)
        dueDate = try c.decodeIfPresent(Date.self, forKey: .dueDate)
    }
 
    func encode(to encoder: Encoder) throws {
        var c = encoder.container(keyedBy: CodingKeys.self)
        try c.encode(title, forKey: .title)
        try c.encode(isCompleted, forKey: .isCompleted)
        try c.encodeIfPresent(dueDate, forKey: .dueDate)
    }
 
    init(title: String) {
        self.title = title
        self.isCompleted = false
    }
}
 
@Model
final class Tag {
    var name: String
    var colorHex: String
    var tasks: [Task] = []
 
    init(name: String, colorHex: String) {
        self.name = name
        self.colorHex = colorHex
    }
}

deleteRule: .cascade(タスクをプロジェクトと一緒に削除)と .nullify(タグ削除時にタスクは残す)の使い分けが一度で正しく出てくることが多く、「後から気づいてマイグレーション」という事故を防げます。

テストコード生成:実装と同時に積み上げる

Unit Test は「後で書く」と決めてしまうと、ほぼ書かずにリリースになります。私もそのパターンを繰り返してきました。Antigravity を使うと、実装したコードを選択して「このビジネスロジック用のテストを生成して」と投げるだけで XCTest ベースのコードが出てきます。

テスト対象はビジネスロジックに絞るのが現実的です。View のレイアウトをテストするより、@Observable モデルのメソッドをテストする方が長く使えます。

// テスト対象モデル
@Observable
final class TaskFilter {
    var showCompleted: Bool = false
    var selectedTag: Tag? = nil
 
    func filter(_ tasks: [Task]) -> [Task] {
        tasks.filter { task in
            let completedMatch = showCompleted ? true : \!task.isCompleted
            let tagMatch: Bool
            if let tag = selectedTag {
                tagMatch = task.tags.contains(where: { $0.name == tag.name })
            } else {
                tagMatch = true
            }
            return completedMatch && tagMatch
        }
    }
}
 
// Antigravity が生成したテストコード
final class TaskFilterTests: XCTestCase {
    var filter: TaskFilter\!
    var pendingTask: Task\!
    var completedTask: Task\!
 
    override func setUp() {
        filter = TaskFilter()
        pendingTask = Task(title: "未完了タスク")
        completedTask = Task(title: "完了タスク")
        completedTask.isCompleted = true
    }
 
    func testHidesCompletedTasksByDefault() {
        let result = filter.filter([pendingTask, completedTask])
        XCTAssertEqual(result.count, 1)
        XCTAssertFalse(result.first\!.isCompleted)
    }
 
    func testShowsAllTasksWhenEnabled() {
        filter.showCompleted = true
        let result = filter.filter([pendingTask, completedTask])
        XCTAssertEqual(result.count, 2)
    }
 
    func testFiltersByTag() {
        let tag = Tag(name: "仕事", colorHex: "#3B82F6")
        pendingTask.tags = [tag]
        filter.selectedTag = tag
 
        let result = filter.filter([pendingTask, completedTask])
        XCTAssertEqual(result.count, 1)
        XCTAssertEqual(result.first?.title, "未完了タスク")
    }
}

テストが機能ごとに積み上がると、リファクタリングへの心理的なハードルが下がります。これが個人開発の長期メンテナンスに思いのほか効いてきます。

テスト生成の詳細な戦略についてはAntigravity AI でテストコードを自動生成にまとめています。

Antigravity が苦手な場面も知っておく

SwiftUI 開発で使っていて気づいた、Antigravity の精度が落ちやすいパターンを共有します。

大きなファイルをそのまま貼り付けた場合
200行を超える View ファイルをコンテキストとして渡すと、応答の精度が落ちることがあります。View を適切にコンポーネント分割して1ファイルを小さく保つことが、AI のためにも SwiftUI の設計原則としても正しい方向です。

#Preview 周り
Antigravity が生成した View コードで #Preview が動かないことがあります。@Environment@Query を使う View は、プレビュー用のインメモリ ModelContainer を設定する必要があります。「このViewのPreviewコードも書いて」と追加で依頼すると、ModelContainer(for: [Task.self], configurations: ModelConfiguration(isStoredInMemoryOnly: true)) を含むコードが出てきます。

Xcode 環境との連携を深めたい場合はXcode × Antigravity iOS 開発ガイドも参考になります。

今日から試せる一歩

今取り組んでいる View の中で「この書き方で合っているか自信がない」と感じている部分を1つ選んでみてください。Antigravity への問いかけを「どう書けばいいですか」ではなく、「今こう書いているのですが、iOS 17 のベストプラクティスとどこが違いますか」にしてみると、修正点だけでなく判断の根拠まで返ってくることが多いです。

コードを生成させることより、判断の理由を引き出すことに使うと、Antigravity は SwiftUI 開発の学習加速装置としても機能します。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-04-05
AntigravityでiOSプライバシーマニフェストを自動生成・管理する実践ガイド
iOS 17以降で必須となったプライバシーマニフェスト(PrivacyInfo.xcprivacy)をAntigravityで自動生成・管理する方法を徹底解説。APIアクセス理由の記述からApp Store審査通過まで、実践的なワークフローを紹介します。
アプリ開発2026-07-03
ダウンロードサイズの逆行に提出直前で気づかないために — AAB/IPA のサイズ予算をエージェントの週次ゲートで守る
メディエーションSDKやアセットの追加で静かに膨らむダウンロードサイズに、bundletool と App Thinning レポートを使ったサイズ台帳・予算ゲート・増分の内訳特定で歯止めをかける設計をまとめます。
アプリ開発2026-06-28
メディエーションを増やしたら iOS の計測が静かに痩せた — SKAdNetwork ID のズレを4アプリで揃える
メディエーションに新しいパートナーを足したのに iOS の収益が伸びず、SKAdNetwork ID の登録漏れが原因だった実体験。Info.plist の SKAdNetworkItems を4アプリで突き合わせ、Antigravity エージェントに照合させて人間が採否を決める運用を紹介します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →