[英]Swift async await: how to use with several non async delegates?
I have created this simple example, it is a UITextField
with an autocompletion ability, displaying a table view showing asynchronous data that evolves as the user types in the text field.我创建了这个简单的示例,它是一个具有自动完成功能的UITextField
,显示了一个表格视图,其中显示了随着用户在文本字段中键入而演变的异步数据。
TextField文本域
import UIKit
protocol TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField,
_ components: @escaping ([String]) -> Void
)
}
class TextField: UITextField {
var components: [String] = []
var tableView = UITableView(frame: .zero)
var autocompletionDelegate: TextFieldDelegate? { didSet { setupUI() } }
// Actions
@objc private func didUpdateText() {
autocompletionDelegate?.autocompletedComponents(self) { [weak self] components in
guard let weakSelf = self else {
return
}
weakSelf.components = components
weakSelf.tableView.reloadData()
weakSelf.updateUI()
}
}
// Event
override func becomeFirstResponder() -> Bool {
tableView.isHidden = false
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
tableView.isHidden = true
return super.resignFirstResponder()
}
// Init
override init(frame: CGRect) {
super.init(frame: frame)
internalInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
internalInit()
}
private func internalInit() {
addTarget(
self,
action: #selector(didUpdateText),
for: .editingChanged
)
}
// UI
private func setupUI() {
tableView.dataSource = self
tableView.delegate = self
tableView.showsVerticalScrollIndicator = false
tableView.removeFromSuperview()
superview?.addSubview(tableView)
}
private func updateUI() {
let tableViewHeight = Double(min(5, max(0, components.count))) * 44.0
tableView.frame = CGRect(
origin: CGPoint(
x: frame.origin.x,
y: frame.origin.y + frame.size.height
),
size: CGSize(
width: frame.size.width,
height: tableViewHeight
)
)
}
}
extension TextField: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = components[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
44.0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
components.count
}
}
ViewController视图控制器
import MapKit
import UIKit
class ViewController: UIViewController {
private var completion: (([MKLocalSearchCompletion]) -> Void)?
private var searchCompleter = MKLocalSearchCompleter()
@IBOutlet weak var textField: TextField!
override func viewDidLoad() {
super.viewDidLoad()
searchCompleter.delegate = self
textField.autocompletionDelegate = self
}
}
extension ViewController: TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField,
_ components: @escaping ([String]) -> Void
) {
if completion == nil {
completion = { results in
components(results.map { $0.title })
}
}
searchCompleter.queryFragment = textField.text ?? ""
}
}
extension ViewController: MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completion?(completer.results)
}
}
In this example the view controller uses data from MapKit
.在此示例中,视图控制器使用来自MapKit
的数据。 Now I would like to get rid of the @escaping
blocks and replace them with the new async/await syntax.现在我想摆脱@escaping
块并用新的 async/await 语法替换它们。
I have started rewriting the TextField
code:我已经开始重写TextField
代码:
protocol TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField
) async -> [String]
}
@objc private func didUpdateText() {
Task {
let autocompletedComponents = await autocompletionDelegate?.autocompletedComponents(self) ?? []
components = autocompletedComponents
tableView.reloadData()
updateUI()
}
}
However I am stuck in the ViewController, because I don't know what to do with the completion block I was using until now.但是我被困在 ViewController 中,因为我不知道如何处理我直到现在使用的完成块。
Thank you for your help谢谢您的帮助
Here's an implementation using Combine
's PassThroughSubject
to send the array from MKLocalSearchCompleterDelegate
to your autocompletedComponents
function which then returns that array to be used in TextField
这是一个使用Combine
的PassThroughSubject
将数组从MKLocalSearchCompleterDelegate
发送到您的autocompletedComponents
函数的实现,该函数然后返回该数组以在TextField
中使用
In ViewController
:在ViewController
中:
class ViewController: UIViewController {
private var searchCompleter = MKLocalSearchCompleter()
var cancellables = Set<AnyCancellable>()
var publisher = PassthroughSubject<[String], Never>()
@IBOutlet weak var textField: TextField!
override func viewDidLoad() {
super.viewDidLoad()
searchCompleter.delegate = self
textField.autocompletionDelegate = self
}
}
extension ViewController: TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField
) async -> [String] {
searchCompleter.queryFragment = textField.text ?? ""
return await withCheckedContinuation { continuation in
publisher
.sink { array in
continuation.resume(returning: array)
}.store(in: &cancellables)
}
}
}
extension ViewController: MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
publisher.send(completer.results.map({$0.title}))
}
}
Or you could use your completion
closure it would look like this:或者你可以使用你的completion
闭包,它看起来像这样:
func autocompletedComponents(
_ textField: TextField,
_ components: @escaping ([String]) -> Void
) {
return await withCheckedContinuation { continuation in
if completion == nil {
completion = { results in
continuation.resume(returning: results.map { $0.title })
}
}
searchCompleter.queryFragment = textField.text ?? ""
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.