How to add a TextField to Alert in SwiftUI?

前端 未结 9 1935
温柔的废话
温柔的废话 2021-02-03 21:33

Anyone an idea how to create an Alert in SwiftUI that contains a TextField?

\"sample_image\"

相关标签:
9条回答
  • 2021-02-03 22:07

    This is an example based on the SwiftUI Sheet class that displays a dialog with a prompt, a text field, and the classic OK and Dismiss button

    First lets make our Dialog class, which will pop when user want to edit a value:

    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
    

    Now we use it this way in the caller View:

    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
    
    0 讨论(0)
  • 2021-02-03 22:11

    Alert is quite limited at the moment, but you can roll your own solution in pure SwiftUI.

    Here's a simple implementation of a custom alert with a text field.

    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)
                }
            }
        }
    
    }
    

    And a View extension to use it:

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

    Demo:

    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!")
        }
    }
    
    0 讨论(0)
  • 2021-02-03 22:12

    I found modals and alerts in SwiftUI to be lacking several features. For instance, there doesn't seem to be a way of presenting a modal with FormSheet style.

    When I need to present a complex alert (such as one with textfields), I create a pure SwiftUI view with all the content of the alert, and then present it as a FormSheet using a UIHostController.

    If you do not have a UIViewController around to call present(), you can always use the root view controller.

    With this approach you get some nice features, such as the standard alert animation both going in, and out. You can also drag the alert down to dismiss it.

    The alert view also moves up when the keyboard appears.

    This works nicely on iPad. On iPhone, FormSheet is full screen so you may need to tweak the code to find a solution. I think this will give you a good starting point.

    It is something like this:

    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))
        }
    }
    

    If you find yourself using this a lot, the button row may be encapsulated in a separate view for easy reuse.

    0 讨论(0)
提交回复
热议问题