简体   繁体   English

Swift 异步等待:如何与多个非异步委托一起使用?

[英]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这是一个使用CombinePassThroughSubject将数组从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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM