如何在 SwiftUI 中将 TextField 添加到警报? [英] How to add a TextField to Alert in SwiftUI?

查看:44
本文介绍了如何在 SwiftUI 中将 TextField 添加到警报?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人知道如何在包含 TextField 的 SwiftUI 中创建警报吗?

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

推荐答案

由于 SwiftUI 提供的 Alert 视图无法完成您确实需要使用的工作UIAlertController 来自 UIKit.理想情况下,我们需要一个 TextFieldAlert 视图,我们可以用与显示 SwiftUI 提供的 Alert 相同的方式来显示它:

As the Alert view provided by SwiftUI doesn't do the job you will need indeed to use UIAlertController from UIKit. Ideally we want a TextFieldAlert view that we can presented in the same way we would present the Alert provided by SwiftUI:

struct MyView: View {

  @Binding var alertIsPresented: Bool
  @Binding var text: String? // this is updated as the user types in the text field

  var body: some View {
    Text("My Demo View")
      .textFieldAlert(isPresented: $alertIsPresented) { () -> TextFieldAlert in
        TextFieldAlert(title: "Alert Title", message: "Alert Message", text: self.$text)
    }
  }
}

我们可以通过编写几个类并在 View 扩展中添加修饰符来实现这一点.

We can achieve this writing a couple of classes and adding a modifier in a View extension.

1) TextFieldAlertViewController 创建一个 UIAlertController(当然还有一个文本字段)并在它出现在屏幕上时呈现它.用户对文本字段的更改会反映到在初始化期间传递的 Binding 中.

1) TextFieldAlertViewController creates a UIAlertController (with a text field of course) and presents it when it appears on screen. User changes to the text field are reflected into a Binding<String> that is passed during initializazion.

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(title: String, message: String?, text: Binding<String?>, isPresented: Binding<Bool>?) {
    self.alertTitle = title
    self.message = message
    self._text = text
    self.isPresented = isPresented
    super.init(nibName: nil, bundle: nil)
  }

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

  // MARK: - Dependencies
  private let alertTitle: String
  private let message: String?
  @Binding private var text: String?
  private var isPresented: Binding<Bool>?

  // 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: alertTitle, message: message, preferredStyle: .alert)

    // add a textField and create a subscription to update the `text` binding
    vc.addTextField { [weak self] textField in
      guard let self = self else { return }
      self.subscription = NotificationCenter.default
        .publisher(for: UITextField.textDidChangeNotification, object: textField)
        .map { ($0.object as? UITextField)?.text }
        .assign(to: \.text, on: self)
    }

    // create a `Done` action that updates the `isPresented` binding when tapped
    // this is just for Demo only but we should really inject
    // an array of buttons (with their title, style and tap handler)
    let action = UIAlertAction(title: "Done", style: .default) { [weak self] _ in
      self?.isPresented?.wrappedValue = false
    }
    vc.addAction(action)
    present(vc, animated: true, completion: nil)
  }
}

2) TextFieldAlert 使用 UIViewControllerRepresentable 协议包装 TextFieldAlertViewController 以便它可以在 SwiftUI 中使用.

2) TextFieldAlert wraps TextFieldAlertViewController using the UIViewControllerRepresentable protocol so that it can be used within SwiftUI.

struct TextFieldAlert {

  // MARK: Properties
  let title: String
  let message: String?
  @Binding var text: String?
  var isPresented: Binding<Bool>? = nil

  // MARK: Modifiers
  func dismissable(_ isPresented: Binding<Bool>) -> TextFieldAlert {
    TextFieldAlert(title: title, message: message, text: $text, isPresented: isPresented)
  }
}

extension TextFieldAlert: UIViewControllerRepresentable {

  typealias UIViewControllerType = TextFieldAlertViewController

  func makeUIViewController(context: UIViewControllerRepresentableContext<TextFieldAlert>) -> UIViewControllerType {
    TextFieldAlertViewController(title: title, message: message, text: $text, isPresented: isPresented)
  }

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

3) TextFieldWrapper 是一个简单的 ZStack,后面有一个 TextFieldAlert(仅当 isPresented 为真时) 和正面视图.呈现视图是唯一可见的.

3) TextFieldWrapper is a simple ZStack with a TextFieldAlert on the back (only if isPresented is true) and a presenting view on the front. The presenting view is the only one visibile.

struct TextFieldWrapper<PresentingView: View>: View {

  @Binding var isPresented: Bool
  let presentingView: PresentingView
  let content: () -> TextFieldAlert

  var body: some View {
    ZStack {
      if (isPresented) { content().dismissable($isPresented) }
      presentingView
    }
  }  
}

4) textFieldAlert 修饰符允许我们将任何 SwiftUI 视图平滑地包装在 TextFieldWrapper 中并获得所需的行为.

4) The textFieldAlert modifier allows us to smoothly wrap any SwiftUI view in a TextFieldWrapper and obtain the desired behaviour.

extension View {
  func textFieldAlert(isPresented: Binding<Bool>,
                      content: @escaping () -> TextFieldAlert) -> some View {
    TextFieldWrapper(isPresented: isPresented,
                     presentingView: self,
                     content: content)
  }
}

这篇关于如何在 SwiftUI 中将 TextField 添加到警报?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆