77 votes

Comment ajouter un champ de texte à une alerte dans SwiftUI ?

Quelqu'un sait-il comment créer une alerte dans SwiftUI qui contient un champ de texte ?

sample_image

4voto

Kirill Points 609

Comme cela a déjà été mentionné Alert ne fournit pas beaucoup de fonctionnalités et est donc presque inutile dans tous les cas non standard lorsqu'il est utilisé dans SwiftUI.

J'ai fini par trouver une solution un peu extensive - View qui peut se comporter comme une alerte avec un niveau de personnalisation élevé.

  1. Créer ViewModel pour le popUp :

    struct UniAlertViewModel {
    
     let backgroundColor: Color = Color.gray.opacity(0.4)
     let contentBackgroundColor: Color = Color.white.opacity(0.8)
     let contentPadding: CGFloat = 16
     let contentCornerRadius: CGFloat = 12
    }
  2. nous devons également configurer les boutons, à cette fin, ajoutons un type supplémentaire :

    struct UniAlertButton {
    
     enum Variant {
    
         case destructive
         case regular
     }
    
     let content: AnyView
     let action: () -> Void
     let type: Variant
    
     var isDestructive: Bool {
         type == .destructive
     }
    
     static func destructive<Content: View>(
         @ViewBuilder content: @escaping () -> Content
     ) -> UniAlertButton {
         UniAlertButton(
             content: content,
             action: { /* close */ },
             type: .destructive)
     }
    
     static func regular<Content: View>(
         @ViewBuilder content: @escaping () -> Content,
         action: @escaping () -> Void
     ) -> UniAlertButton {
         UniAlertButton(
             content: content,
             action: action,
             type: .regular)
     }
    
     private init<Content: View>(
         @ViewBuilder content: @escaping () -> Content,
         action: @escaping () -> Void,
         type: Variant
     ) {
         self.content = AnyView(content())
         self.type = type
         self.action = action
     }
    }
  3. ajouter une vue qui peut devenir notre popUp personnalisable :

    struct UniAlert<Presenter, Content>: View where Presenter: View, Content: View {
    
     @Binding private (set) var isShowing: Bool
    
     let displayContent: Content
     let buttons: [UniAlertButton]
     let presentationView: Presenter
     let viewModel: UniAlertViewModel
    
     private var requireHorizontalPositioning: Bool {
         let maxButtonPositionedHorizontally = 2
         return buttons.count > maxButtonPositionedHorizontally
     }
    
     var body: some View {
         GeometryReader { geometry in
             ZStack {
                 backgroundColor()
    
                 VStack {
                     Spacer()
    
                     ZStack {
                         presentationView.disabled(isShowing)
                         let expectedWidth = geometry.size.width * 0.7
    
                         VStack {
                             displayContent
                             buttonsPad(expectedWidth)
                         }
                         .padding(viewModel.contentPadding)
                         .background(viewModel.contentBackgroundColor)
                         .cornerRadius(viewModel.contentCornerRadius)
                         .shadow(radius: 1)
                         .opacity(self.isShowing ? 1 : 0)
                         .frame(
                             minWidth: expectedWidth,
                             maxWidth: expectedWidth
                         )
                     }
    
                     Spacer()
                 }
             }
         }
     }
    
     private func backgroundColor() -> some View {
         viewModel.backgroundColor
             .edgesIgnoringSafeArea(.all)
             .opacity(self.isShowing ? 1 : 0)
     }
    
     private func buttonsPad(_ expectedWidth: CGFloat) -> some View {
         VStack {
             if requireHorizontalPositioning {
                 verticalButtonPad()
             } else {
                 Divider().padding([.leading, .trailing], -viewModel.contentPadding)
                 horizontalButtonsPadFor(expectedWidth)
             }
         }
     }
    
     private func verticalButtonPad() -> some View {
         VStack {
             ForEach(0..<buttons.count) {
                 Divider().padding([.leading, .trailing], -viewModel.contentPadding)
                 let current = buttons[$0]
    
                 Button(action: {
                     if !current.isDestructive {
                         current.action()
                     }
    
                     withAnimation {
                         self.isShowing.toggle()
                     }
                 }, label: {
                     current.content.frame(height: 35)
                 })
             }
         }
     }
    
     private func horizontalButtonsPadFor(_ expectedWidth: CGFloat) -> some View {
         HStack {
             let sidesOffset = viewModel.contentPadding * 2
             let maxHorizontalWidth = requireHorizontalPositioning ?
                 expectedWidth - sidesOffset :
                 expectedWidth / 2 - sidesOffset
    
             Spacer()
    
             if !requireHorizontalPositioning {
                 ForEach(0..<buttons.count) {
                     if $0 != 0 {
                         Divider().frame(height: 44)
                     }
                     let current = buttons[$0]
    
                     Button(action: {
                         if !current.isDestructive {
                             current.action()
                         }
    
                         withAnimation {
                             self.isShowing.toggle()
                         }
                     }, label: {
                         current.content
                     })
                     .frame(maxWidth: maxHorizontalWidth, minHeight: 44)
                 }
             }
             Spacer()
         }
     }
    }
  4. pour simplifier l'utilisation, ajoutons l'extension à View :

    extension View {
    
     func assemblyAlert<Content>(
         isShowing: Binding<Bool>,
         viewModel: UniAlertViewModel,
         @ViewBuilder content: @escaping () -> Content,
         actions: [UniAlertButton]
     ) -> some View where Content: View {
         UniAlert(
             isShowing: isShowing,
             displayContent: content(),
             buttons: actions,
             presentationView: self,
             viewModel: viewModel)
     }
    }

Et l'usage :

struct ContentView: View {

    @State private var isShowingAlert: Bool = false
    @State private var text: String = ""

    var body: some View {
        VStack {
            Button(action: {
                withAnimation {
                    isShowingAlert.toggle()
                }
            }, label: {
                Text("Show alert")
            })
        }
        .assemblyAlert(isShowing: $isShowingAlert,
                       viewModel: UniAlertViewModel(),
                       content: {
                        Text("title")
                        Image(systemName: "phone")
                            .scaleEffect(3)
                            .frame(width: 100, height: 100)
                        TextField("enter text here", text: $text)
                        Text("description")
                       }, actions: buttons)
        }
   }
 }

Démonstration :

enter image description here

3voto

Tristan Leblanc Points 71

Voici un exemple basé sur l'interface SwiftUI Sheet classe qui affiche une boîte de dialogue avec une invite, un champ de texte et les boutons classiques OK et Rejeter.

enter image description here

enter image description here

Tout d'abord, créons notre Dialog qui apparaîtra lorsque l'utilisateur voudra modifier une valeur :

import SwiftUI

struct Dialog: View {
    @Environment(\.presentationMode) var presentationMode

    /// Edited value, passed from outside
    @Binding var value: String?

    /// Prompt message
    var prompt: String = ""

    /// The value currently edited
    @State var fieldValue: String

    /// Init the Dialog view
    /// Passed @binding value is duplicated to @state value while editing
    init(prompt: String, value: Binding<String?>) {
        _value = value
        self.prompt = prompt
        _fieldValue = State<String>(initialValue: value.wrappedValue ?? "")
    }

    var body: some View {
        VStack {
            Text(prompt).padding()
            TextField("", text: $fieldValue)
            .frame(width: 200, alignment: .center)
            HStack {
            Button("OK") {
                self.value = fieldValue
                self.presentationMode.wrappedValue.dismiss()
            }
            Button("Dismiss") {
                self.presentationMode.wrappedValue.dismiss()
            }
            }.padding()
        }
        .padding()
    }
}

#if DEBUG
struct Dialog_Previews: PreviewProvider {

    static var previews: some View {
        var name = "John Doe"
        Dialog(prompt: "Name", value: Binding<String?>.init(get: { name }, set: {name = $0 ?? ""}))
    }
}
#endif

Maintenant, nous l'utilisons de cette façon dans la vue de l'appelant :

import SwiftUI

struct ContentView: View {
    /// Is the input dialog displayed
    @State var dialogDisplayed = false

    /// The name to edit
    @State var name: String? = nil

    var body: some View {
        VStack {
            Text(name ?? "Unnamed").frame(width: 200).padding()
            Button(name == nil ? "Set Name" : "Change Name") {
                dialogDisplayed = true
            }
            .sheet(isPresented: $dialogDisplayed) {
                Dialog(prompt: name == nil ? "Enter a name" : "Enter a new name", value: $name)
            }
            .onChange(of: name, perform: { value in
                print("Name Changed : \(value)")
            }
            .padding()
        }
        .padding()
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

3voto

Ranjith Kumar Points 8125

Étape 1 : Faire de la vue racine une pile Z

Étape 2 : Ajouter une variable pour afficher/masquer

 @State var showAlert = false

Étape 3 : Ajoutez cette mise en page personnalisée dans la vue racine (ZStack).

  if $showAlert.wrappedValue {
            ZStack() {
                Color.grayBackground
                VStack {
                    //your custom layout text fields buttons 

                }.padding()
            }
            .frame(width: 300, height: 180,alignment: .center)
            .cornerRadius(20).shadow(radius: 20)
        }

2voto

Lister Lee Points 11

Basé sur l'idée de la tanzolone

import Foundation
import Combine
import SwiftUI

class TextFieldAlertViewController: UIViewController {

    /// Presents a UIAlertController (alert style) with a UITextField and a `Done` button
    /// - Parameters:
    ///   - title: to be used as title of the UIAlertController
    ///   - message: to be used as optional message of the UIAlertController
    ///   - text: binding for the text typed into the UITextField
    ///   - isPresented: binding to be set to false when the alert is dismissed (`Done` button tapped)
    init(isPresented: Binding<Bool>, alert: TextFieldAlert) {
        self._isPresented = isPresented
        self.alert = alert
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @Binding
    private var isPresented: Bool
    private var alert: TextFieldAlert

    // MARK: - Private Properties
    private var subscription: AnyCancellable?

    // MARK: - Lifecycle
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        presentAlertController()
    }

    private func presentAlertController() {
        guard subscription == nil else { return } // present only once

        let vc = UIAlertController(title: alert.title, message: alert.message, preferredStyle: .alert)
        // add a textField and create a subscription to update the `text` binding
        vc.addTextField {
            // TODO: 
            // $0.placeholder = alert.placeholder
            // $0.keyboardType = alert.keyboardType
            // $0.text = alert.defaultValue ?? ""
            $0.text = self.alert.defaultText
        }
        if let cancel = alert.cancel {
            vc.addAction(UIAlertAction(title: cancel, style: .cancel) { _ in
                //                self.action(nil)
                self.isPresented = false
            })
        }
        let textField = vc.textFields?.first
        vc.addAction(UIAlertAction(title: alert.accept, style: .default) { _ in
            self.isPresented = false
            self.alert.action(textField?.text)
        })
        present(vc, animated: true, completion: nil)
    }
}

struct TextFieldAlert {

    let title: String
    let message: String?
    var defaultText: String = ""
    public var accept: String = "".localizedString // The left-most button label
    public var cancel: String? = "".localizedString // The optional cancel (right-most) button label
    public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog

}

struct AlertWrapper:  UIViewControllerRepresentable {

    @Binding var isPresented: Bool
    let alert: TextFieldAlert

    typealias UIViewControllerType = TextFieldAlertViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<AlertWrapper>) -> UIViewControllerType {
        TextFieldAlertViewController(isPresented: $isPresented, alert: alert)
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<AlertWrapper>) {
        // no update needed
    }
}

struct TextFieldWrapper<PresentingView: View>: View {

    @Binding var isPresented: Bool
    let presentingView: PresentingView
    let content: TextFieldAlert

    var body: some View {
        ZStack {
            if (isPresented) {
                AlertWrapper(isPresented: $isPresented, alert: content)
            }
            presentingView
        }
    }
}

extension View {

    func alert(isPresented: Binding<Bool>, _ content: TextFieldAlert) -> some View {
        TextFieldWrapper(isPresented: isPresented, presentingView: self, content: content)
    }

}

Mode d'emploi

        xxxView
        .alert(isPresented: $showForm, TextFieldAlert(title: "", message: "") { (text) in
            if text != nil {
                self.saveGroup(text: text!)
            }
        })

-2voto

tsniso Points 323
func dialog(){

       let alertController = UIAlertController(title: "Contry", message: "Write contrt code here", preferredStyle: .alert)

        alertController.addTextField { (textField : UITextField!) -> Void in
            textField.placeholder = "Country code"
        }

        let saveAction = UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in

            let secondTextField = alertController.textFields![0] as UITextField
            print("county code : ",secondTextField)

        })

        let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil )

        alertController.addAction(saveAction)
        alertController.addAction(cancelAction)

        UIApplication.shared.windows.first?.rootViewController?.present(alertController, animated: true, completion: nil)

    }

Utilisation

Button(action: { self.dialog()})
 {
Text("Button")
.foregroundColor(.white).fontWeight(.bold)
 }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X