高校倫理1436242 views
教育149163 views
小学算数1197561 views
ヒストリア286805 views
雑学1473058 views
中学理科1628254 views
Computer366393 views
LaTeX959099 views
高校日本史190117 views
高校国語786623 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 を直接ラップするカスタムビューを作成する方法も検討に値する。