How to add a TextField to Alert in SwiftUI?

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

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

\"sample_image\"

9条回答
  •  慢半拍i
    慢半拍i (楼主)
    2021-02-03 22:02

    As already was mentioned Alert is provide not many functionality and so almost useless in any non-standard cases when using in SwiftUI.

    I ended up with a bit extensive solution - View that may behave as alert with high customisation level.

    1. Create ViewModel for 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. we also need to configure buttons, for this purpose let's add one more type:

      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(
           @ViewBuilder content: @escaping () -> Content
       ) -> UniAlertButton {
           UniAlertButton(
               content: content,
               action: { /* close */ },
               type: .destructive)
       }
      
       static func regular(
           @ViewBuilder content: @escaping () -> Content,
           action: @escaping () -> Void
       ) -> UniAlertButton {
           UniAlertButton(
               content: content,
               action: action,
               type: .regular)
       }
      
       private init(
           @ViewBuilder content: @escaping () -> Content,
           action: @escaping () -> Void,
           type: Variant
       ) {
           self.content = AnyView(content())
           self.type = type
           self.action = action
       }
      }
      
    3. add View that can become our customizable popUp:

      struct UniAlert: 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.. some View {
           HStack {
               let sidesOffset = viewModel.contentPadding * 2
               let maxHorizontalWidth = requireHorizontalPositioning ?
                   expectedWidth - sidesOffset :
                   expectedWidth / 2 - sidesOffset
      
               Spacer()
      
               if !requireHorizontalPositioning {
                   ForEach(0..
    4. to simplify usage let's add extension to View:

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

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

    Demo:

提交回复
热议问题