MENU

【SwiftUI】【TCA】カスタムテキストフィールドの実装

目次

導入

Composable Architecture (TCA) を使用して、SwiftUIでカスタムテキストフィールドを作成する方法について解説します。この記事では、以下の4つのSwiftファイルを用いて、カスタムテキストフィールドの機能を実装し、親ビューでそのテキストフィールドを使用する方法を説明します。

  1. CustomTextFieldFeature.swift
  2. CustomTextField.swift
  3. ParentFeature.swift
  4. 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エンジニアが皆さんをお導きいたします!
ぜひメンターを受けてみてください〜
メンターはこちら

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Rio@iOSエンジニアのアバター Rio@iOSエンジニア 経営者兼モバイルアプリエンジニア

都内のモバイルアプリ開発会社経営者。
モバイルアプリの新規の請負開発及び保守運用を引き受ける。
Denso→Honda→現在
#RxSwift #MVVM #Firebase #Python3

コメント

コメントする

目次