MathPython491378 views
ヒストリア284143 views
小学社会308636 views
中学理科1626207 views
英語607877 views
高校物理158224 views
中学英語808712 views
中学社会667106 views
高校国語785655 views
高校化学2913383 views
Help
Tools

English

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 を直接ラップするカスタムビューを作成する方法も検討に値する。