web-dev-qa-db-fra.com

Comment ajouter un TextField à Alert dans SwiftUI?

Quelqu'un a une idée comment créer une alerte dans SwiftUI qui contient un TextField?

image_échantillon

17
Captain Vril

Alert est assez limité pour le moment, mais vous pouvez rouler votre propre solution en SwiftUI pur.

Voici une implémentation simple d'une alerte personnalisée avec un champ de texte.

struct TextFieldAlert<Presenting>: View where Presenting: View {

    @Binding var isShowing: Bool
    @Binding var text: String
    let presenting: Presenting
    let title: String

    var body: some View {
        GeometryReader { (deviceSize: GeometryProxy) in
            ZStack {
                self.presenting
                    .disabled(isShowing)
                VStack {
                    Text(self.title)
                    TextField(self.$text)
                    Divider()
                    HStack {
                        Button(action: {
                            withAnimation {
                                self.isShowing.toggle()
                            }
                        }) {
                            Text("Dismiss")
                        }
                    }
                }
                .padding()
                .background(Color.white)
                .frame(
                    width: deviceSize.size.width*0.7,
                    height: deviceSize.size.height*0.7
                )
                .shadow(radius: 1)
                .opacity(self.isShowing ? 1 : 0)
            }
        }
    }

}

Et une extension View pour l'utiliser:

extension View {

    func textFieldAlert(isShowing: Binding<Bool>,
                        text: Binding<String>,
                        title: String) -> some View {
        TextFieldAlert(isShowing: isShowing,
                       text: text,
                       presenting: self,
                       title: title)
    }

}

Démo :

enter image description here

struct ContentView : View {

    @State private var isShowingAlert = false
    @State private var alertInput = ""

    var body: some View {
        NavigationView {
            VStack {
                Button(action: {
                    withAnimation {
                        self.isShowingAlert.toggle()
                    }
                }) {
                    Text("Show alert")
                }
            }
            .navigationBarTitle(Text("A List"), displayMode: .large)
        }
        .textFieldAlert(isShowing: $isShowingAlert, text: $alertInput, title: "Alert!")
    }
}
6
Matteo Pacini

Vous pouvez simplement utiliser UIAlertController directement. Pas besoin de lancer votre propre interface de dialogue d'alerte:

private func alert() {
    let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
    alert.addTextField() { textField in
        textField.placeholder = "Enter some text"
    }
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in })
    showAlert(alert: alert)
}

func showAlert(alert: UIAlertController) {
    if let controller = topMostViewController() {
        controller.present(alert, animated: true)
    }
}

private func keyWindow() -> UIWindow? {
    return UIApplication.shared.connectedScenes
    .filter {$0.activationState == .foregroundActive}
    .compactMap {$0 as? UIWindowScene}
    .first?.windows.filter {$0.isKeyWindow}.first
}

private func topMostViewController() -> UIViewController? {
    guard let rootController = keyWindow()?.rootViewController else {
        return nil
    }
    return topMostViewController(for: rootController)
}

private func topMostViewController(for controller: UIViewController) -> UIViewController {
    if let presentedController = controller.presentedViewController {
        return topMostViewController(for: presentedController)
    } else if let navigationController = controller as? UINavigationController {
        guard let topController = navigationController.topViewController else {
            return navigationController
        }
        return topMostViewController(for: topController)
    } else if let tabController = controller as? UITabBarController {
        guard let topController = tabController.selectedViewController else {
            return tabController
        }
        return topMostViewController(for: topController)
    }
    return controller
}

La plupart de ce code est juste un passe-partout pour trouver le ViewController qui devrait présenter l'alerte. Appelez alert() par ex. à partir du action d'un bouton:

struct TestView: View {
    var body: some View {
        Button(action: { alert() }) { Text("click me") }
     }
}

Attention cependant, il semble y avoir un bogue dans la version bêta 5 et ultérieure qui peut parfois provoquer le blocage de l'émulateur une fois qu'un champ de texte est affiché: Xcode 11 beta 5: l'interface utilisateur se bloque lors de l'ajout de textFields dans UIAlertController

6
Fabian Streitel

Bien que ce ne soit pas exactement la même chose, si tout ce que vous recherchez est une vue native de type modal avec une zone d'édition, vous pouvez utiliser un popover . Il fonctionne hors de la boîte (moins un bogue de dimensionnement ) sans avoir besoin de traverser la hiérarchie des vues.

3
Senseful

J'ai trouvé que les modaux et les alertes dans SwiftUI manquaient de plusieurs fonctionnalités. Par exemple, il ne semble pas y avoir de moyen de présenter un modal avec le style FormSheet.

Lorsque je dois présenter une alerte complexe (comme une avec des champs de texte), je crée une vue SwiftUI pure avec tout le contenu de l'alerte, puis je la présente sous la forme d'une feuille de formulaire à l'aide d'un UIHostController .

Si vous n'avez pas de UIViewController pour appeler present (), vous pouvez toujours utiliser le contrôleur de vue racine.

Avec cette approche, vous obtenez des fonctionnalités intéressantes, telles que l'animation d'alerte standard à la fois pour entrer et sortir. Vous pouvez également faire glisser l'alerte vers le bas pour la désactiver.

La vue d'alerte remonte également lorsque le clavier apparaît.

Cela fonctionne bien sur iPad. Sur iPhone, FormSheet est en plein écran, vous devrez donc peut-être modifier le code pour trouver une solution. Je pense que cela vous donnera un bon point de départ.

enter image description here

C'est quelque chose comme ça:

struct ContentView : View {
    @State private var showAlert = false

    var body: some View {
        VStack {
            Button(action: {
                let alertHC = UIHostingController(rootView: MyAlert())

                alertHC.preferredContentSize = CGSize(width: 300, height: 200)
                alertHC.modalPresentationStyle = UIModalPresentationStyle.formSheet

                UIApplication.shared.windows[0].rootViewController?.present(alertHC, animated: true)

            }) {
                Text("Show Alert")
            }
        }
    }
}

struct MyAlert: View {
    @State private var text: String = ""

    var body: some View {

        VStack {
            Text("Enter Input").font(.headline).padding()

            TextField($text, placeholder: Text("Type text here")).textFieldStyle(.roundedBorder).padding()
            Divider()
            HStack {
                Spacer()
                Button(action: {
                    UIApplication.shared.windows[0].rootViewController?.dismiss(animated: true, completion: {})
                }) {

                    Text("Done")
                }
                Spacer()

                Divider()

                Spacer()
                Button(action: {
                    UIApplication.shared.windows[0].rootViewController?.dismiss(animated: true, completion: {})
                }) {
                    Text("Cancel")
                }
                Spacer()
            }.padding(0)


            }.background(Color(white: 0.9))
    }
}

Si vous vous en servez beaucoup, la rangée de boutons peut être encapsulée dans une vue séparée pour une réutilisation facile.

3
kontiki