数学講師2862298 views
高校倫理1434987 views
高校生物550226 views
小学算数1196702 views
Computer365920 views
中学理科1627564 views
小学理科717868 views
高校化学2915516 views
教育149067 views
小学社会308942 views

SwiftUI TextFieldStyle の作成

TextFieldStyle プロトコルを使うと、テキスト入力フィールドの見た目をカスタマイズできる。標準の .roundedBorder では物足りない場合に、独自のデザインを適用できる。

TextFieldStyle の基本

TextFieldStyle プロトコルは _body(configuration:) メソッドを実装する。

struct UnderlineTextFieldStyle: TextFieldStyle {
    func _body(configuration: TextField<_Label>) -> some View {
        configuration
            .padding(.vertical, 8)
            .overlay(
                Rectangle()
                    .frame(height: 1)
                    .foregroundColor(.gray),
                alignment: .bottom
            )
    }
}

configuration はそのまま TextField として扱える。これに装飾を追加して返す。

TextField("メールアドレス", text: $email)
    .textFieldStyle(UnderlineTextFieldStyle())

フォーカス状態の反映

@FocusState と組み合わせて、フォーカス時に見た目を変えるスタイルを作る。

struct FocusableTextFieldStyle: TextFieldStyle {
    @FocusState private var isFocused: Bool
    
    func _body(configuration: TextField<_Label>) -> some View {
        configuration
            .focused($isFocused)
            .padding(12)
            .background(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(isFocused ? Color.blue : Color.gray.opacity(0.3), lineWidth: isFocused ? 2 : 1)
            )
    }
}

フォーカス時にボーダーが青く太くなる。

ラベル付きスタイル

フィールドの上にラベルを表示するスタイル。

struct LabeledTextFieldStyle: TextFieldStyle {
    var label: String
    
    func _body(configuration: TextField<_Label>) -> some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(label)
                .font(.caption)
                .foregroundColor(.secondary)
            
            configuration
                .padding(12)
                .background(Color.gray.opacity(0.1))
                .cornerRadius(8)
        }
    }
}

extension TextFieldStyle where Self == LabeledTextFieldStyle {
    static func labeled(_ label: String) -> LabeledTextFieldStyle {
        LabeledTextFieldStyle(label: label)
    }
}
TextField("", text: $username)
    .textFieldStyle(.labeled("ユーザー名"))

アイコン付きスタイル

struct IconTextFieldStyle: TextFieldStyle {
    var icon: String
    
    func _body(configuration: TextField<_Label>) -> some View {
        HStack(spacing: 8) {
            Image(systemName: icon)
                .foregroundColor(.gray)
            
            configuration
        }
        .padding(12)
        .background(
            RoundedRectangle(cornerRadius: 8)
                .fill(Color.gray.opacity(0.1))
        )
    }
}

extension TextFieldStyle where Self == IconTextFieldStyle {
    static func icon(_ systemName: String) -> IconTextFieldStyle {
        IconTextFieldStyle(icon: systemName)
    }
}
TextField("検索", text: $searchText)
    .textFieldStyle(.icon("magnifyingglass"))

TextField("メール", text: $email)
    .textFieldStyle(.icon("envelope"))

バリデーション対応スタイル

struct ValidatedTextFieldStyle: TextFieldStyle {
    var isValid: Bool?
    
    func _body(configuration: TextField<_Label>) -> some View {
        HStack {
            configuration
            
            if let isValid = isValid {
                Image(systemName: isValid ? "checkmark.circle.fill" : "xmark.circle.fill")
                    .foregroundColor(isValid ? .green : .red)
            }
        }
        .padding(12)
        .background(
            RoundedRectangle(cornerRadius: 8)
                .stroke(borderColor, lineWidth: 1)
        )
    }
    
    private var borderColor: Color {
        guard let isValid = isValid else { return .gray.opacity(0.3) }
        return isValid ? .green : .red
    }
}

入力のバリデーション結果を視覚的にフィードバックできる。

マテリアルデザイン風スタイル

struct MaterialTextFieldStyle: TextFieldStyle {
    var label: String
    @FocusState private var isFocused: Bool
    @State private var hasText: Bool = false
    
    func _body(configuration: TextField<_Label>) -> some View {
        ZStack(alignment: .leading) {
            Text(label)
                .foregroundColor(isFocused ? .blue : .gray)
                .font(isFocused || hasText ? .caption : .body)
                .offset(y: isFocused || hasText ? -20 : 0)
                .animation(.easeInOut(duration: 0.2), value: isFocused)
            
            configuration
                .focused($isFocused)
                .onChange(of: isFocused) { _ in
                    // テキストの有無を確認するロジックが必要
                }
        }
        .padding(.top, 16)
        .overlay(
            Rectangle()
                .frame(height: isFocused ? 2 : 1)
                .foregroundColor(isFocused ? .blue : .gray),
            alignment: .bottom
        )
    }
}

フォーカス時にラベルが上に移動する、マテリアルデザインのようなアニメーションを実現できる。

標準スタイルとの比較

.roundedBorder

最もシンプル。設定画面やフォームで広く使える。

.plain

装飾なし。カスタムデザインのベースとして使う。

TextFieldStyle はアンダースコア付きの _body メソッドを使用するため、将来の API 変更に注意が必要だ。安定性を重視する場合は、TextField を直接ラップするカスタムビューを作成する方法も検討に値する。