SwiftUI:如何在搜索栏的文本更改时触发 api 调用以检索数据源
SwiftUI: How to trigger api call to retrieve data source when Search Bar's text is changed
我使用 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 : [=11=].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 已经从服务器调用 return 过滤后的搜索结果。更改代码以移除过滤器{}。我还添加了点击手势以触发导航到电影详细信息视图并在您触底时调用第 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()
}
}
现在它在我的应用程序中是什么样子的。只剩下一件事,向下滚动列表时关闭键盘。
您需要使用如下的 searchBarDelegate 方法:
func searchBar(UISearchBar, shouldChangeTextIn: NSRange, replacementText: String) -> Bool
每次输入您都会收到来自用户的回调,在此函数中您可以触发对服务器的 api 调用。
您可以使用闭包。在这里,我将 onTextChanged: (String) -> 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 : [=10=].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
}
}
我使用 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 : [=11=].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 已经从服务器调用 return 过滤后的搜索结果。更改代码以移除过滤器{}。我还添加了点击手势以触发导航到电影详细信息视图并在您触底时调用第 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()
}
}
现在它在我的应用程序中是什么样子的。只剩下一件事,向下滚动列表时关闭键盘。
您需要使用如下的 searchBarDelegate 方法:
func searchBar(UISearchBar, shouldChangeTextIn: NSRange, replacementText: String) -> Bool
每次输入您都会收到来自用户的回调,在此函数中您可以触发对服务器的 api 调用。
您可以使用闭包。在这里,我将 onTextChanged: (String) -> 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 : [=10=].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
}
}