ワンクリックで
swiftui-feature
SwiftUIアプリに新機能を追加します。Use when ユーザーが「新しい画面追加して」「機能実装して」「Viewを作って」と言ったとき。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
SwiftUIアプリに新機能を追加します。Use when ユーザーが「新しい画面追加して」「機能実装して」「Viewを作って」と言ったとき。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
iOSシミュレーターをコンピューターユースで視覚的にテストします。スクリーンショットを撮影してUI要素を確認し、座標ベースでタップ・スワイプ操作を行いテスト結果を報告します。Use when ユーザーが「シミュレーターでテストして」「iOSシミュレーターを視覚的にテストして」「computer useでシミュレーターを操作して」「シミュレーターの画面確認して」と言ったとき。
Xcode MCP(mcpbridge)を Claude Code に追加します。Use when ユーザーが「Xcode MCP入れて」「シミュレーター操作できるようにして」「mcpbridge設定して」と言ったとき。
xcodebuildとfastlaneでiOSアプリをビルド・アーカイブし、App Store/TestFlightに申請します。Use when ユーザーが「TestFlightにアップして」「App Storeに申請して」「リリースして」と言ったとき。
Step-by-step release checklist for ClipKit iOS app submission to TestFlight and App Store. Use when preparing release, uploading to TestFlight, submitting to App Store, bumping version, or creating a release.
このプロジェクトで実際に踏んだ iOS 開発の落とし穴と対策。CoreData, AVFoundation, AVAudioSession など。CoreData の設定変更・新機能追加・マイグレーション対応時に必ず参照すること。
Step-by-step release checklist for Smart Player iOS app submission to App Store. Use when preparing release, updating version, submitting to App Store, or mentioning App Store submission.
| name | swiftui-feature |
| description | SwiftUIアプリに新機能を追加します。Use when ユーザーが「新しい画面追加して」「機能実装して」「Viewを作って」と言ったとき。 |
SwiftUI アプリに新機能を追加する際の定型フローを提供するスキルです。View、ViewModel/Manager、State Management、テストコードの追加をベストプラクティスに従って実装します。
このスキルは、以下のパターンをサポートします:
1. View + ObservableObject パターン
View.swift
└─ @StateObject var manager: FeatureManager
2. View + Singleton パターン
View.swift
└─ @ObservedObject var store: FeatureStore.shared
3. View + @State パターン(シンプルな画面用)
View.swift
└─ @State private var data
4. View + Environment パターン
View.swift
└─ @EnvironmentObject var settings: AppSettings
プロジェクトの既存パターンを確認:
# プロジェクト構造を確認
find . -name "*.swift" -type f | grep -E "(View|Manager|Store)" | head -20
# 既存のStateパターンを確認
grep -r "@StateObject\|@ObservedObject\|@EnvironmentObject" --include="*.swift" | head -10
確認事項:
ユーザーと以下を確認:
@StateObject - View が Manager を所有@ObservedObject - 親から渡されるシンプルな画面の場合:
// Views/FeatureView.swift
import SwiftUI
struct FeatureView: View {
@State private var data: String = ""
@State private var isLoading: Bool = false
var body: some View {
VStack {
if isLoading {
ProgressView()
} else {
Text(data)
}
}
.navigationTitle("Feature")
.task {
await loadData()
}
}
private func loadData() async {
isLoading = true
defer { isLoading = false }
// データ取得ロジック
try? await Task.sleep(nanoseconds: 1_000_000_000)
data = "Loaded"
}
}
#Preview {
NavigationStack {
FeatureView()
}
}
複雑な状態管理が必要な場合:
// Managers/FeatureManager.swift
import Foundation
import Combine
@MainActor
class FeatureManager: ObservableObject {
@Published var data: [Item] = []
@Published var isLoading: Bool = false
@Published var errorMessage: String?
func loadData() async {
isLoading = true
errorMessage = nil
do {
// データ取得ロジック
try await Task.sleep(nanoseconds: 1_000_000_000)
data = [Item(id: 1, name: "Item 1")]
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
func addItem(_ item: Item) {
data.append(item)
}
func deleteItem(at offsets: IndexSet) {
data.remove(atOffsets: offsets)
}
}
// Models/Item.swift
struct Item: Identifiable {
let id: Int
var name: String
}
// Views/FeatureView.swift
import SwiftUI
struct FeatureView: View {
@StateObject private var manager = FeatureManager()
var body: some View {
List {
ForEach(manager.data) { item in
Text(item.name)
}
.onDelete(perform: manager.deleteItem)
}
.navigationTitle("Feature")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Add", action: addItem)
}
}
.task {
await manager.loadData()
}
.overlay {
if manager.isLoading {
ProgressView()
}
}
.alert("Error", isPresented: .constant(manager.errorMessage != nil)) {
Button("OK") { manager.errorMessage = nil }
} message: {
if let error = manager.errorMessage {
Text(error)
}
}
}
private func addItem() {
let newItem = Item(id: manager.data.count + 1, name: "New Item")
manager.addItem(newItem)
}
}
アプリ全体で共有する状態の場合:
// Stores/FeatureStore.swift
import Foundation
import Combine
class FeatureStore: ObservableObject {
static let shared = FeatureStore()
@Published var items: [Item] = []
private init() {
loadFromUserDefaults()
}
func addItem(_ item: Item) {
items.append(item)
saveToUserDefaults()
}
private func loadFromUserDefaults() {
// UserDefaults からロード
}
private func saveToUserDefaults() {
// UserDefaults に保存
}
}
// Views/FeatureView.swift
struct FeatureView: View {
@ObservedObject var store = FeatureStore.shared
var body: some View {
List(store.items) { item in
Text(item.name)
}
}
}
// MainTabView.swift
TabView {
ExistingView()
.tabItem {
Label("Existing", systemImage: "house")
}
FeatureView() // 新しく追加
.tabItem {
Label("Feature", systemImage: "star")
}
}
NavigationStack {
List {
NavigationLink("Go to Feature") {
FeatureView()
}
}
}
struct ParentView: View {
@State private var showFeature = false
var body: some View {
Button("Show Feature") {
showFeature = true
}
.sheet(isPresented: $showFeature) {
FeatureView()
}
}
}
// Tests/FeatureManagerTests.swift
import XCTest
@testable import YourApp
@MainActor
final class FeatureManagerTests: XCTestCase {
var manager: FeatureManager!
override func setUp() {
super.setUp()
manager = FeatureManager()
}
override func tearDown() {
manager = nil
super.tearDown()
}
func testLoadData() async {
// 初期状態
XCTAssertEqual(manager.data.count, 0)
XCTAssertFalse(manager.isLoading)
// データロード
await manager.loadData()
// 結果確認
XCTAssertGreaterThan(manager.data.count, 0)
XCTAssertFalse(manager.isLoading)
}
func testAddItem() {
let item = Item(id: 1, name: "Test")
manager.addItem(item)
XCTAssertEqual(manager.data.count, 1)
XCTAssertEqual(manager.data.first?.name, "Test")
}
func testDeleteItem() {
manager.addItem(Item(id: 1, name: "Item 1"))
manager.addItem(Item(id: 2, name: "Item 2"))
manager.deleteItem(at: IndexSet(integer: 0))
XCTAssertEqual(manager.data.count, 1)
XCTAssertEqual(manager.data.first?.name, "Item 2")
}
}
# ビルドして実行
xcodebuild -project YourApp.xcodeproj -scheme YourApp -destination 'platform=iOS Simulator,name=iPhone 16' build
# テスト実行
xcodebuild -project YourApp.xcodeproj -scheme YourApp test -destination 'platform=iOS Simulator,name=iPhone 16'
ユーザーが言うこと: "設定画面を追加して"
実行されること:
SettingsView.swift を作成(@State パターン)結果: タブに設定画面が追加され、基本的な設定項目が表示される
ユーザーが言うこと: "ユーザー一覧画面を作って、APIからデータ取得する"
実行されること:
Models/User.swift を作成Managers/UserManager.swift を作成(ObservableObject)Views/UserListView.swift を作成UserManagerTests.swift を作成結果: APIからユーザーデータを取得して一覧表示する画面が完成
ユーザーが言うこと: "お気に入り機能を追加して、どの画面からでもアクセスできるようにして"
実行されること:
Stores/FavoriteStore.swift を Singleton で作成FavoriteListView.swift を作成結果: アプリ全体でお気に入り状態が共有され、どの画面からでもアクセス可能
ユーザーが言うこと: "SpeedView にダークモード切り替えを追加して"
実行されること:
SpeedView.swift を確認@AppStorage でダークモード設定を管理.preferredColorScheme() で反映結果: SpeedView にダークモード切り替えが追加される
Cannot find 'FeatureManager' in scope原因: Manager ファイルが Target に追加されていない
解決方法:
@StateObject 初期化エラー原因: @StateObject は定数(let)または初期値が必要
解決方法:
// ❌ 間違い
@StateObject var manager: FeatureManager
// ✅ 正しい
@StateObject private var manager = FeatureManager()
原因: Preview で必要な依存関係が不足
解決方法:
#Preview {
NavigationStack {
FeatureView()
.environmentObject(AppSettings.shared) // 必要な Environment を追加
}
}
原因: メインスレッドで更新されていない
解決方法:
@MainActor
class FeatureManager: ObservableObject {
@Published var data: [Item] = []
func loadData() async {
// @MainActor により自動的にメインスレッドで実行される
data = await fetchData()
}
}
原因: UserDefaults への保存タイミングが適切でない
解決方法:
class FeatureStore: ObservableObject {
@Published var items: [Item] = [] {
didSet {
saveToUserDefaults() // 変更時に自動保存
}
}
}
references/architecture-guide.md を参照| パターン | 使用ケース | 例 |
|---|---|---|
@State | View 内で完結する簡単な状態 | Toggle、TextField の値 |
@StateObject | View が所有する複雑な状態 | API Manager、Form Manager |
@ObservedObject | 親から渡される状態 | 共有 Manager |
@EnvironmentObject | アプリ全体で共有する設定 | Theme、User Session |
| Singleton | アプリ全体で永続化する状態 | UserDefaults ベースの Store |
@Published は変更時に View を再描画するため、頻繁に変わる値は注意ForEach + Identifiable を使用@State の didSet や computed property は避け、専用メソッドで実行AsyncImage を使用.accessibilityLabel() でラベルを追加.accessibilityHint() で操作のヒントを追加