如何在 SwiftUI 中制作下拉搜索栏

问题描述

我想使用 SwiftUI 在我的应用程序中创建一个下拉搜索栏,并且必须与 iOS13 兼容,所以我无法使用 ScrollViewReader。我尝试使用 Introspect,但我不明白它的作用以及它是如何工作的,因为除了下拉刷新之外,文档没有解释如何为 ScrollView 做一些事情。我使用了 TextField 的 introspect 成为并退出了 FirstResponder,它可以工作,但是当我使用 introspect for scrollview 时,该功能仅在滚动视图首次出现时运行。

@H_404_2@@State private var willSearch = false ... .introspectTextField{ textfield in textfield.returnKeyType = .done if willSearch { textfield.becomeFirstResponder() }else{ textfield.resignFirstResponder() } } // this works when `willSearch` changed value

这就是我的 ScrollView 自省的样子

@H_404_2@extension UIScrollView { var isBouncing: Bool { return (contentOffset.y + contentInset.top < 0) } } ... .introspectScrollView { scroll in if scroll.isDragging{ self.willSearch = scroll.isBouncing; } } // this doesn't work

我也尝试通过跟随 this suggestion 来检测滚动,但效果不是很好,而且我可以听到我的 MacBook 风扇比平时更响亮,因此它可能会影响性能

这大致就是我希望它的行为Pull down search bar as seen in Telegram

解决方法

我更喜欢使用 UIKit 来实现应用内导航,只使用 SwiftUI 来构建屏幕 UI。所以我使用 UIHostingController + UINavigationController 并且可以轻松集成 UISearchController。例如像这样:

import UIKit
import SwiftUI

class ViewController: UIHostingController<Content>,UISearchResultsUpdating {

    private let viewModel = ViewModel()

    init() {
        super.init(rootView: Content(viewModel: viewModel))
        title = "Test"
    }

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

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchResultsUpdater = self
        self.navigationItem.searchController = searchController
        navigationController?.view.setNeedsLayout() // this is needed for iOS 13 only
    }

    func updateSearchResults(for searchController: UISearchController) {
        viewModel.searchDidChange(searchController.searchBar.text ?? "")
    }

}

struct Content: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {
        ScrollView {
            VStack {
                ForEach(viewModel.items,id: \.self) { item in
                    Text(item)
                        .frame(maxWidth: .infinity)
                }
            }
        }
    }
}

class ViewModel: ObservableObject {
    @Published var items: [String]
    private let allItems: [String]
    init() {
        self.allItems = (0..<100).map { "\($0)" }
        self.items = allItems
    }

    func searchDidChange(_ text: String) {
        items = allItems.filter {
            $0.starts(with: text)
        }
    }
}

警告:您必须禁用 Main.storyboard (https://sarunw.com/posts/how-to-create-new-xcode-project-without-storyboard/) 并手动加载根控制器。例如像这样:

import UIKit

class SceneDelegate: UIResponder,UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene,willConnectTo session: UISceneSession,options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: scene)
        window.rootViewController = UINavigationController(rootViewController: ViewController())
        window.makeKeyAndVisible()
        self.window = window
    }

}

enter image description here