高校倫理1434987 views
中学数学622001 views
高校国語786245 views
世界の国561479 views
いろは2993462 views
教育149067 views
雑学1472898 views
MathPython493120 views
中学理科1627564 views
高校物理158628 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 を直接ラップするカスタムビューを作成する方法も検討に値する。