SwiftUI:如何在搜索栏的文本更改时触发 api 调用以检索数据源

Posted

技术标签:

【中文标题】SwiftUI:如何在搜索栏的文本更改时触发 api 调用以检索数据源【英文标题】:SwiftUI: How to trigger api call to retrieve data source when Search Bar's text is changed 【发布时间】:2020-04-17 12:34:18 【问题描述】:

我使用 UIKit 实现了一个搜索栏,将其包装在 MovieSearchView 中。当用户输入一些关键字时,它将在以下列表视图中显示搜索结果。

数据 searchResults 来自 API 调用 self.model.getMovieSearchResults(),但在我的代码中,从未触发 api 调用。所以我想知道当搜索栏中的搜索文本发生更改时,我应该在哪里放置条件检查onAppear() 以触发api调用?

///搜索视图

import SwiftUI

struct MovieSearchView: View 
    @ObservedObject var model = MovieListViewModel()
    @State private var searchText: String = ""

    var body: some View 
        NavigationView 
            VStack 
                SearchBar(text: $searchText)
                .onAppear() 
                    // If searchText has a value, then call api to fetch movies data.
                    if self.searchText.isEmpty == false 
                        self.model.getMovieSearchResults(for: self.searchText)
                    
                
                List 
                    ForEach(model.searchResults.filter 
                        self.searchText.isEmpty ? true : $0.title.lowercased().contains(self.searchText.lowercased())
                    )  movie in
                        Text(movie.title)
                    
                
            
        
    


struct MovieSearchView_Previews: PreviewProvider 
    static var previews: some View 
        MovieSearchView()
    

///搜索栏

import SwiftUI

struct SearchBar: UIViewRepresentable 

    @Binding var text: String

    class Coordinator: NSObject, UISearchBarDelegate 

        @Binding var text: String

        init(text: Binding<String>) 
            _text = text
        

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) 
            text = searchText
        
    

    func makeCoordinator() -> SearchBar.Coordinator 
        return Coordinator(text: $text)
    

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar 
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        return searchBar
    

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) 
        uiView.text = text
    

更新:

只需意识到我不需要再次进行过滤,因为 API 调用已经从服务器返回过滤后的搜索结果。更改代码以删除过滤器。我还添加了点击手势以触发导航到电影详细信息视图并在您点击底部时调用第 2 页。

更多更新:

当用户清除搜索文本时清除搜索结果。

当用户开始编辑时,在搜索栏上显示取消按钮,并且 按下取消按钮时关闭键盘。

搜索栏

import SwiftUI

struct SearchBar: UIViewRepresentable 

    @Binding var text: String
    var onTextChanged: (String) -> Void

    class Coordinator: NSObject, UISearchBarDelegate 

        @Binding var text: String
        var onTextChanged: (String) -> Void

        init(text: Binding<String>, onTextChanged: @escaping (String) -> Void) 
            _text = text
            self.onTextChanged = onTextChanged
        

        // Show cancel button when the user begins editing the search text
        func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) 
            searchBar.showsCancelButton = true
        

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) 
            text = searchText
            onTextChanged(text)
        

        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) 
            text = ""
            searchBar.showsCancelButton = false
            searchBar.endEditing(true)
            // Send back empty string text to search view, trigger self.model.searchResults.removeAll()
            onTextChanged(text)
        
    

    func makeCoordinator() -> SearchBar.Coordinator 
        return Coordinator(text: $text, onTextChanged: onTextChanged)
    

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar 
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.placeholder = "Search TMDB"
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        searchBar.showsCancelButton = true
        return searchBar
    

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) 
        uiView.text = text
    

搜索视图

import SwiftUI

struct MovieSearchView: View 
    @ObservedObject var model = MovieListViewModel()
    @State private var searchText: String = ""
    @State private var selectedId = -1
    @State private var showSheet = false
    @State private var page = 1

    var body: some View 
        List 
            SearchBar(text: $searchText, onTextChanged: searchMovies)

            ForEach(0..<model.searchResults.count, id: \.self)  i in
                MovieListRow(movie: self.model.searchResults[i])
                    .onTapGesture 
                        self.selectedId = self.model.searchResults[i].id
                        self.showSheet.toggle()
                    
                .onAppear() 
                    if i == self.model.searchResults.count - 1 
                        self.page += 1
                        self.model.getMovieSearchResults(for: self.searchText, page: self.page)
                    
                
            
        
        .sheet(isPresented: $showSheet) 
            SingleMovieView(movieId: self.selectedId)
        
    

    func searchMovies(for searchText: String) 
        if !searchText.isEmpty 
            self.model.getMovieSearchResults(for: self.searchText, page: self.page)
         else 
            // remove search result when a user clear keyword.
            self.model.searchResults.removeAll()
        
    


struct MovieSearchView_Previews: PreviewProvider 
    static var previews: some View 
        MovieSearchView()
    

现在它在我的应用中是什么样子的。只剩下一件事,向下滚动列表时关闭键盘。

【问题讨论】:

【参考方案1】:

您可以使用闭包。这里我在 SearchBar 中添加了onTextChanged: (String) -&gt; Void

电影搜索视图

import SwiftUI

struct MovieSearchView: View 
    @ObservedObject var model = MovieListViewModel()
    @State private var searchText: String = ""

    var body: some View 
        NavigationView 
            VStack 
                SearchBar(text: $searchText, onTextChanged: searchMovies)
                
                List 
                    ForEach(model.searchResults.filter 
                        self.searchText.isEmpty ? true : $0.title.lowercased().contains(self.searchText.lowercased())
                    )  movie in
                        Text(movie.title)
                    
                
            
        
    
    
    func searchMovies(for searchText: String) 
        if !searchText.isEmpty 
            model.getMovieSearchResults(for: searchText)
        
    


struct MovieSearchView_Previews: PreviewProvider 
    static var previews: some View 
        MovieSearchView()
    

搜索栏

import SwiftUI

struct SearchBar: UIViewRepresentable 
    @Binding var text: String
    var onTextChanged: (String) -> Void
    
    class Coordinator: NSObject, UISearchBarDelegate 
        var onTextChanged: (String) -> Void
        @Binding var text: String
        
        init(text: Binding<String>, onTextChanged: @escaping (String) -> Void) 
            _text = text
            self.onTextChanged = onTextChanged
        
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) 
            text = searchText
            onTextChanged(text)
        
    
    
    func makeCoordinator() -> SearchBar.Coordinator 
        return Coordinator(text: $text, onTextChanged: onTextChanged)
    
    
    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar 
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        return searchBar
    
    
    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) 
        uiView.text = text
    

【讨论】:

我试了一下,效果很好。据我了解,此解决方案将闭包分配给SearchBar,其中包括api调用语句。而在SearchBar里面,会回调MovieSearchView中定义的func,有意思。所以我们不需要调用SearchBar中的模型,我们只需发送一个闭包。我说的对吗? 是的,SearchBar 可以在其他视图中使用,因为它不会引用您的模型来工作。它只知道它必须使用String 参数调用onTextChanged 感谢您的回答,真的很有帮助。我再次更新了有问题的代码,因为我刚刚意识到我做了两次过滤工作,从 api 调用返回的数据已经包含过滤的结果。我真是个白痴:)【参考方案2】:

您需要使用如下的 searchBarDelegate 方法:

func searchBar(UISearchBar, shouldChangeTextIn: NSRange, replacementText: String) -> Bool

每次键入时,您都会收到来自用户的回调,并且在此函数中,您可以触发对服务器的 api 调用。

【讨论】:

感谢您的回答,我已经尝试过了。但事实证明,添加这个函数并在其中调用 api 后,我无法在搜索栏中输入任何单词。 您可以添加与文本计数相关的检查。

以上是关于SwiftUI:如何在搜索栏的文本更改时触发 api 调用以检索数据源的主要内容,如果未能解决你的问题,请参考以下文章

如何让 TextField 值更改触发 SwiftUI 中另一条数据的更新?

如何在 SwiftUI 中触发状态更改/重绘切换更改

当用户在 SwiftUI 中触摸 GoogleMaps 的标记时如何更改 UI 状态?

自定义“可搜索”搜索字段 SwiftUI iOS 15

使用tabview更改选项卡时如何使SwiftUI中的计时器保持触发

仅当搜索栏文本不为空时,如何运行过滤器功能?