From ios-team
iOS画面実装のアーキテクチャ・設計思想スキル。Reduxパターンに基づくSingle Root State、Reducerによるビジネスロジック分離、テスタビリティを重視した実装を支援。使用シーン:(1)「このプロトタイプを本番実装にして」などの本格実装リクエスト (2)「状態管理を整理して」などのアーキテクチャ適用リクエスト (3) 画面の状態管理やテスト可能な設計が必要な場合 (4)「この画面をテストしやすくして」などのリファクタリングリクエスト
npx claudepluginhub hiragram/claude-code-plugins --plugin ios-teamThis skill uses the workspace's default tool permissions.
Reduxパターンによる画面実装の設計指針。
Applies proven SwiftUI UI patterns for navigation, sheets, async state, reusable screens, and app wiring. Useful for creating, refactoring screens or scaffolding new iOS projects.
Guides SwiftUI architecture for iOS 26+ apps: refactoring views, patterns like MVVM/TCA/Apple's native/Coordinator, property wrappers, testability.
Guides pure SwiftUI architecture: state management with @State/@Observable/@Environment, shared services, navigation, UI components, async patterns without ViewModels.
Share bugs, ideas, or general feedback.
Reduxパターンによる画面実装の設計指針。
JavaScriptエコシステムで生まれた状態管理パターン。3つの原則に基づく:
iOSではTCA (The Composable Architecture) が有名だが、このスキルではTCAを使わずに同じ思想を実現する。
App/
├── AppState.swift # ルート状態(Single Source of Truth)
├── AppAction.swift # 全アクション定義
├── AppStore.swift # Store(状態保持 + Action dispatch)
├── Features/
│ ├── Order/
│ │ ├── OrderState.swift # 機能別の状態(AppStateの一部)
│ │ ├── OrderAction.swift # 機能別のアクション
│ │ ├── OrderReducer.swift # 純粋関数(テスト対象)
│ │ └── OrderView.swift # View(パラメータを受け取るだけ)
│ └── Profile/
│ └── ...
└── Services/ # 副作用(API、DB等)
└── OrderService.swift
struct AppState {
var order = OrderState()
var profile = ProfileState()
var navigation = NavigationState()
}
struct OrderState {
var orders: [Order] = []
var isLoading = false
var error: Error?
}
状態は値型(struct)で定義。AppStateが唯一の真実の源。
enum AppAction {
case order(OrderAction)
case profile(ProfileAction)
}
enum OrderAction {
case loadOrders
case ordersLoaded([Order])
case ordersFailed(Error)
case selectOrder(Order)
}
ビジネスロジックを純粋関数として定義。テストの主要対象。
enum OrderReducer {
static func reduce(state: inout OrderState, action: OrderAction) {
switch action {
case .loadOrders:
state.isLoading = true
state.error = nil
case .ordersLoaded(let orders):
state.orders = orders
state.isLoading = false
case .ordersFailed(let error):
state.error = error
state.isLoading = false
case .selectOrder(let order):
// 選択状態の更新など
break
}
}
}
enum AppReducer {
static func reduce(state: inout AppState, action: AppAction) {
switch action {
case .order(let action):
OrderReducer.reduce(state: &state.order, action: action)
case .profile(let action):
ProfileReducer.reduce(state: &state.profile, action: action)
}
}
}
状態の保持とAction dispatchを担当。副作用(API呼び出し等)もここで処理。
@MainActor
final class AppStore: ObservableObject {
@Published private(set) var state = AppState()
// Dependencies
private let orderService: OrderServiceProtocol
init(orderService: OrderServiceProtocol) {
self.orderService = orderService
}
func send(_ action: AppAction) {
// 1. Reducerで状態更新
AppReducer.reduce(state: &state, action: action)
// 2. 副作用の実行
Task {
await handleSideEffects(action)
}
}
private func handleSideEffects(_ action: AppAction) async {
switch action {
case .order(.loadOrders):
do {
let orders = try await orderService.fetchOrders()
send(.order(.ordersLoaded(orders)))
} catch {
send(.order(.ordersFailed(error)))
}
default:
break
}
}
}
Viewはパラメータを受け取り、表示とアクション送信のみを担当。
@main
struct MyApp: App {
@StateObject private var store = AppStore(
orderService: OrderService()
)
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(store)
}
}
}
struct OrderListView: View {
// 必要な値だけを受け取る
let orders: [Order]
let isLoading: Bool
let onOrderTap: (Order) -> Void
let onRefresh: () -> Void
var body: some View {
Group {
if isLoading {
ProgressView()
} else {
List(orders) { order in
OrderRow(order: order)
.onTapGesture { onOrderTap(order) }
}
.refreshable { onRefresh() }
}
}
}
}
// 親から呼び出す
struct OrderContainerView: View {
@EnvironmentObject var store: AppStore
var body: some View {
OrderListView(
orders: store.state.order.orders,
isLoading: store.state.order.isLoading,
onOrderTap: { store.send(.order(.selectOrder($0))) },
onRefresh: { store.send(.order(.loadOrders)) }
)
}
}
store.state.order.ordersが変わればSwiftUIが自動で差分更新プロトタイプ内の@StateをFeature Stateに移動:
// Before(プロトタイプ)
struct OrderView: View {
@State private var orders: [Order] = []
@State private var isLoading = false
}
// After(Feature State)
struct OrderState {
var orders: [Order] = []
var isLoading = false
}
ユーザー操作とイベントをAction enumに。非同期結果も含める:
enum OrderAction {
// ユーザー操作
case loadOrders
case selectOrder(Order)
// 非同期結果
case ordersLoaded([Order])
case ordersFailed(Error)
}
状態更新ロジックを純粋関数に:
enum OrderReducer {
static func reduce(state: inout OrderState, action: OrderAction) {
// 同期的な状態更新のみ
}
}
Storeで非同期処理を実行:
private func handleSideEffects(_ action: AppAction) async {
// API呼び出しなど
}
Viewをパラメータ受け取り形式に変換。
Reducerのテストを作成(純粋関数なのでテストしやすい)。
@Stateを持たず、パラメータを受け取っているか| ファイル | 内容 |
|---|---|
references/state-management.md | 状態設計の詳細パターン |
references/testing-guide.md | Reducerテストの書き方 |