ios - SwiftUI:如何在搜索栏的文本更改时触发 api 调用以检索数据源
问题描述
我使用 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()
}
}
解决方案
您可以使用闭包。在这里,我添加onTextChanged: (String) -> Void
到 SearchBar。
电影搜索查看
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
}
}
推荐阅读
- react-native - java 11 和 12 的 chirp-react-native run-android 失败?
- azure - ASP.NET Core 2.2 身份 - UserManager.ResetPasswordAsync 总是因无效令牌而失败
- keras - Keras:二值图像分割中的像素级不平衡
- amazon-web-services - 如何从 AWS Glue 获取自动附加的弹性网络接口以与子网分离?
- c++ - 使用 while 循环验证时,“并非所有控制路径都返回值”/“控制可能到达非 void 函数的结尾”?
- android-automotive - 如何从 Android Studio 运行 Android Automotive OS 应用程序?
- javascript - “分辨率”媒体查询要参考实际屏幕
- python - 为什么不对分类问题使用均方误差?
- crystal-lang - 递归类型与自身不统一
- algolia - 如何更改搜索框提交按钮图标?