UIKitとSwiftUIでテキストフィールド付きアラートを表示する

2022.07.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

テキストフィールド付きのアラートを表示させる機会があったので、UIKitとSwiftUIでの方法を調べてみました。

環境

  • Xcode 13.3
  • iOS 15.4

テキストフィールド付きアラートを表示

UIKit

デモ

このようなアラートを作成しました。

uikit-alert-with-text-field

コード

import UIKit

class ViewController: UIViewController {

    @IBOutlet private weak var inputtedLabel: UILabel!

    @IBAction private func showAlertWithTextField() {

        var textFieldOnAlert = UITextField()

        let alert = UIAlertController(title: "テキスト入力",
                                      message: nil,
                                      preferredStyle: .alert)
        alert.addTextField { textField in
            textFieldOnAlert = textField
            textFieldOnAlert.returnKeyType = .done
        }

        let doneAction = UIAlertAction(title: "決定", style: .default) { _ in
            self.inputtedLabel.text = textFieldOnAlert.text
        }

        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel)

        alert.addAction(doneAction)
        alert.addAction(cancelAction)
        present(alert, animated: true)
    }
}

Viewの真ん中にあるアラート表示ボタンを押すと、テキストフィールド付きのアラートが表示されます。アラート上のTextFieldに任意のテキストを入力後に決定ボタン、またはキーボードのdoneキーを押すと、TextFieldのテキストがラベルに反映されるようになっています。

addTextField

UIAlertControllerUITextFieldを追加するには、addTextFieldを使用します。

alert.addTextField { textField in
    textFieldOnAlert = textField
    textFieldOnAlert.returnKeyType = .done
}

texFiled.delegate = selfのようにDelegateに準拠させることもできますが、今回は決定ボタンを押した時のテキストを取得したかっただけなのでDelegateは使用しておりません。

doneAction

今回は決定ボタンを押した時にアラート上のtextFieldOnAlert.textinputtedLabel.textに代入しています。

let doneAction = UIAlertAction(title: "決定", style: .default) { _ in
    self.inputtedLabel.text = textFieldOnAlert.text
}

キーボードの決定キーを押した際もこちらの処理が呼ばれます。

SwiftUI

iOS 15までには、SwiftUIの標準でテキストフィールド付きアラートが存在しません。今回はUIViewControllerRepresentableを活用して実現することにしました。

デモ

このようなアラートを作成しました。

swiftui-alert-with-text-field

コード

AlertControllerWithTextFieldContainer

まずはUIAlertControllerをSwiftUIで使えるようにする為に、UIViewControllerRepresentableの構造体を作ります。

struct AlertControllerWithTextFieldContainer: UIViewControllerRepresentable {

    @Binding var textFieldText: String
    @Binding var isPresented: Bool

    let title: String?
    let message: String?
    let placeholderText: String

    func makeUIViewController(context: Context) -> UIViewController {
        return UIViewController()
    }

    // SwiftUIから新しい情報を受け、viewControllerが更新されるタイミングで呼ばれる
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {

        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)

        // TextFieldの追加
        alert.addTextField { textField in
            textField.placeholder = placeholderText
            textField.returnKeyType = .done
        }

        // 決定ボタンアクション
        let doneAction = UIAlertAction(title: "決定", style: .default) { _ in
            if let textField = alert.textFields?.first,
               let text = textField.text {
                textFieldText = text
            }
        }

        // キャンセルボタンアクション
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel)

        alert.addAction(cancelAction)
        alert.addAction(doneAction)

        DispatchQueue.main.async {
            uiViewController.present(alert, animated: true) {
                isPresented = false
            }
        }
    }
}

プロパティの説明ですが、

  • TextfieldText
    • バインディングしてテキストを更新する為の変数
  • isPresented
    • アラートの表示非表示フラグ
  • title
    • アラートのタイトル
  • message
    • アラートのメッセージ
  • placeholderText
    • アラートのテキストフィールドのplaceholderテキスト

SwiftUIから新しい情報を受け、viewControllerが更新されるタイミングで呼ばれるメソッドupdateUIViewControllerでアラートの表示処理を行なっています。

次はこのAlertControllerWithTextFieldContainerをSwiftUIっぽく使えるようにする為に、カスタムModifierを作成します。

カスタムModifierの定義

まずはViewModifierとしてのAlertWithTextFieldを定義します。

// カスタムModifierの定義
struct AlertWithTextField: ViewModifier {
    @Binding var textFieldText: String
    @Binding var isPresented: Bool

    let title: String?
    let message: String?
    let placeholderText: String

    func body(content: Content) -> some View {
        ZStack {
            content

            if isPresented {
                AlertControllerWithTextFieldContainer(textFieldText: $textFieldText,
                                                      isPresented: $isPresented,
                                                      title: title,
                                                      message: message,
                                                      placeholderText: placeholderText)
            }
        }
    }
}
カスタムModifierをSwiftUIっぽく使用できるようにする
extension View {
    // カスタムModifierのメソッド名を alertWithTextField に置き換え
    func alertWithTextField(_ text: Binding<String>, isPresented: Binding<Bool>, title: String?, message: String?, placeholderText: String) -> some View {
        self.modifier(AlertWithTextField(textFieldText: text,
                                         isPresented: isPresented,
                                         title: title,
                                         message: message,
                                         placeholderText: placeholderText))
    }
}

これでViewのModifierとしてalertWithTextFieldが使用できるようになりました。

ContentView

ContentViewで使用した例になります。

import SwiftUI

struct ContentView: View {

    @State private var text = "入力されたテキスト"
    @State private var shouldPresentAlert = false

    var body: some View {

        VStack(spacing: 50) {
            Text(text)
                .bold()

            Button {
                shouldPresentAlert.toggle()
            } label: {
                Text("アラート表示")
            }
        }
        .alertWithTextField($text,
                            isPresented: $shouldPresentAlert,
                            title: "テキスト入力",
                            message: nil,
                            placeholderText: "")
    }
}

アラート表示というボタンを押下した時に、アラートの表示フラグが切り替わり、アラートが表示されるようになっております。

おわりに

SwiftUIでテキストフィールド付きアラートを表示しようと試みましたが、かなり記述量が多くなってしまいました。早くSwiftUIのAlertもテキストフィールドをスマートに追加できるようになるといいですね。

How to add a TextField to Alert in SwiftUIの記事を見ると、iOS 16からはスマートに実装できるようになりそうです。

参考