目次
導入
Composable Architecture (TCA) を使用して、SwiftUIでカスタムテキストフィールドを作成する方法について解説します。この記事では、以下の4つのSwiftファイルを用いて、カスタムテキストフィールドの機能を実装し、親ビューでそのテキストフィールドを使用する方法を説明します。
CustomTextFieldFeature.swift
CustomTextField.swift
ParentFeature.swift
ParentView.swift
成果物
Githubはこちら https://github.com/RyosukeAtsuchi/SwiftUITCA_Demo.git
CustomTextFieldFeature.swift
このファイルでは、テキストフィールドの状態とアクションを管理するためのリデューサーを定義します。
import ComposableArchitecture
struct CustomTextFieldFeature: Reducer {
struct State: Equatable {
@BindingState var text: String = ""
}
enum Action: BindableAction, Equatable {
// output
case submit(keyword: String?)
// private
case binding(BindingAction<State>)
case didTapSubmitbutton
}
var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .submit(_):
return .none
case .binding:
return .none
case .didTapSubmitbutton:
let keyword = state.text
return .run { send in
await send(.submit(keyword: keyword))
}
}
}
}
}
解説
- State:
text
プロパティを持ち、ユーザーが入力するテキストを保持します。 - Action:
BindableAction
プロトコルを採用し、バインディングアクションと「Submit」ボタンがタップされたときのアクションを定義します。 - Reducer:
BindingReducer
を用いてバインディングを処理し、submitTapped
アクションが発生したときのロジックを定義します。
CustomTextField.swift
このファイルでは、SwiftUIのビューとしてカスタムテキストフィールドを実装します。
import SwiftUI
import ComposableArchitecture
struct CustomTextField: View {
let store: StoreOf<CustomTextFieldFeature>
let placeholder: String
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack {
TextField(placeholder, text: viewStore.binding(
get: \.text,
send: { .binding(.set(\.$text, $0)) }
))
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button("Submit") {
viewStore.send(.didTapSubmitbutton)
}
}
}
}
}
解説
- store:
CustomTextFieldFeature
の状態とアクションを管理するためのストアです。 - TextField:
viewStore
のバインディングを使用して、テキストフィールドの入力を管理します。 - Button: 「Submit」ボタンがタップされたときに、
submitTapped
アクションを送信します。
ParentFeature.swift
このファイルでは、親ビューで使用するための状態とアクションを管理するリデューサーを定義します。
import ComposableArchitecture
struct ParentFeature: Reducer {
struct State: Equatable {
var textFieldFeature: CustomTextFieldFeature.State = .init()
var submittedText: String? = ""
var path = StackState<Destination.State>()
}
enum Action {
case textFieldFeature(CustomTextFieldFeature.Action)
case path(StackAction<Destination.State, Destination.Action>)
case navigateToScreenA
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
// MARK: - textFieldReducer
case let .textFieldFeature(.submit(keyword: keyword)):
state.submittedText = keyword
return .none
case .textFieldFeature(_):
return .none
// MARK: - others
case .path:
return .none
case .navigateToScreenA:
state.path.append(.screenA(ScreenAReducer.State(dataFromParent: state.submittedText)))
return .none
}
}
Scope(state: \.textFieldFeature, action: /Action.textFieldFeature) {
CustomTextFieldFeature()
}
.forEach(\.path, action: /Action.path) {
Destination()
}
}
}
struct Destination: Reducer {
enum State: Equatable {
case screenA(ScreenAReducer.State)
}
enum Action {
case screenA(ScreenAReducer.Action)
}
var body: some ReducerOf<Self> {
Scope(state: /State.screenA, action: /Action.screenA) {
ScreenAReducer()
}
}
}
解説
- State: カスタムテキストフィールドの状態と送信されたテキストを保持します。
- Action: カスタムテキストフィールドからのアクションを受け取ります。
- Reducer:
submitTapped
アクションが発生したときに、送信されたテキストを更新し、テキストフィールドをクリアします。
ParentView.swift
このファイルでは、親ビューとしてカスタムテキストフィールドを使用するSwiftUIのビューを実装します。
import SwiftUI
import ComposableArchitecture
struct ParentView: View {
let store: StoreOf<ParentFeature>
var body: some View {
NavigationStackStore(
self.store.scope(state: \.path, action: { .path($0) })
) {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack {
CustomTextField(
store: self.store.scope(
state: \.textFieldFeature,
action: ParentFeature.Action.textFieldFeature
),
placeholder: "Enter text"
)
if let text = viewStore.submittedText {
Text("Submitted Text: \(text)")
}
Button("GOtoScreenA") {
viewStore.send(.navigateToScreenA)
}
}
}
} destination: { state in
switch state {
case .screenA:
CaseLet(/Destination.State.screenA,
action: Destination.Action.screenA,
then: ScreenAView.init
)
}
}
}
}
※おまけ
親Viewを呼び出すコード
struct TCASwiftUIApp: App {
var body: some Scene {
WindowGroup {
ParentView(store: .init(initialState: .init()){ ParentFeature() })
}
}
}
解説
- store:
ParentFeature
の状態とアクションを管理するためのストアです。 - CustomTextField: 子ビューとしてカスタムテキストフィールドを使用し、その状態とアクションをスコープします。
- Text: 送信されたテキストを表示します。
結論
このようにして、Composable Architectureを使用して、SwiftUIでカスタムテキストフィールドを作成し、親ビューでその機能を管理する方法を実装しました。これにより、状態管理とアクションの処理が明確になり、再利用可能なコンポーネントを構築することができます。
年収4桁万円を達成中のiOSエンジニアが皆さんをお導きいたします!
ぜひメンターを受けてみてください〜
メンターはこちら
コメント