بنقرة واحدة
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() で操作のヒントを追加