iOS SwiftUI搜索栏和REST-API [英] iOS SwiftUI Searchbar and REST-API
问题描述
我正在尝试SwiftUI,并希望使用搜索字符串从我的REST API中获取更新.
I'm experimenting with SwiftUI and would like to fetch an update from my REST API with a search string.
但是,我不确定现在如何将这两个组件结合在一起.
However, I'm not sure how to bring the two components together now.
我希望你有个主意.
这是我的代码:
struct ContentView: View {
@State private var searchTerm: String = ""
@ObservedObject var gameData: GameListViewModel = GameListViewModel(searchString: ### SEARCH STRING ???? ###)
var body: some View {
NavigationView{
Group{
// Games werden geladen...
if(self.gameData.isLoading) {
LoadingView()
}
// Games sind geladen:
else{
VStack{
// Suche:
searchBarView(text: self.$searchTerm)
// Ergebnisse:
List(self.gameData.games){ game in
NavigationLink(destination: GameDetailView(gameName: game.name ?? "0", gameId: 0)){
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(game.name ?? "Kein Name gefunden")
.font(.headline)
Text("Cover: \(game.cover?.toString() ?? "0")")
.font(.subheadline)
.foregroundColor(.gray)
}
}
}
}
}
}
}
.navigationBarTitle(Text("Games"))
}
}
}
以及搜索栏的实现:
import Foundation
import SwiftUI
struct searchBarView: 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) {
print(searchText)
text = searchText
}
}
func makeCoordinator() -> searchBarView.Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: UIViewRepresentableContext<searchBarView>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<searchBarView>) {
uiView.text = text
}
}
推荐答案
搜索文本应在视图模型内.
The search text should be inside the view model.
final class GameListViewModel: ObservableObject {
@Published var isLoading: Bool = false
@Published var games: [Game] = []
var searchTerm: String = ""
private let searchTappedSubject = PassthroughSubject<Void, Error>()
private var disposeBag = Set<AnyCancellable>()
init() {
searchTappedSubject
.flatMap {
self.requestGames(searchTerm: self.searchTerm)
.handleEvents(receiveSubscription: { _ in
DispatchQueue.main.async {
self.isLoading = true
}
},
receiveCompletion: { comp in
DispatchQueue.main.async {
self.isLoading = false
}
})
.eraseToAnyPublisher()
}
.replaceError(with: [])
.receive(on: DispatchQueue.main)
.assign(to: \.games, on: self)
.store(in: &disposeBag)
}
func onSearchTapped() {
searchTappedSubject.send(())
}
private func requestGames(searchTerm: String) -> AnyPublisher<[Game], Error> {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
return Fail(error: URLError(.badURL))
.mapError { $0 as Error }
.eraseToAnyPublisher()
}
return URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.mapError { $0 as Error }
.decode(type: [Game].self, decoder: JSONDecoder())
.map { searchTerm.isEmpty ? $0 : $0.filter { $0.title.contains(searchTerm) } }
.eraseToAnyPublisher()
}
}
每次调用onSearchTapped
时,都会触发新游戏的请求.
Each time onSearchTapped
is called, it fires a request for new games.
这里有很多事情-让我们从requestGames
开始.
There's plenty of things going on here - let's start from requestGames
.
我正在使用 JSONPlaceholder 免费API获取一些数据并将其显示在列表中.
I'm using JSONPlaceholder free API to fetch some data and show it in the List.
requestGames
执行网络请求,从接收到的Data
中解码[Game]
.除此之外,还使用搜索字符串对返回的数组进行了过滤(由于免费API的限制-在现实情况下,您将在请求URL中使用查询参数).
requestGames
performs the network request, decodes [Game]
from the received Data
. In addition to that, the returned array is filtered using the search string (because of the free API limitation - in a real world scenario you'd use a query parameter in the request URL).
现在让我们看一下视图模型的构造函数.
Now let's have a look at the view model constructor.
事件的顺序是:
- 获取搜索点击"主题.
- 执行网络请求(
flatMap
) - 在
flatMap
内部,处理加载逻辑(由于isLoading
在下面使用Publisher
在主队列上调度,如果在后台线程上发布了值,则会出现警告). -
replaceError
将发布者的错误类型更改为Never
,这是assign
运算符的要求. -
receiveOn
是必需的,因为由于网络请求,我们可能仍在后台队列中-我们希望将结果发布到主队列中. -
assign
更新视图模型上的数组games
. -
store
将Cancellable
保存在disposeBag
中
- Get the "search tapped" subject.
- Perform a network request (
flatMap
) - Inside the
flatMap
, loading logic is handled (dispatched on the main queue asisLoading
uses aPublisher
underneath, and there will be a warning if a value is published on a background thread). replaceError
changes the error type of the publisher toNever
, which is a requirement for theassign
operator.receiveOn
is necessary as we're probably still in a background queue, thanks to the network request - we want to publish the results on the main queue.assign
updates the arraygames
on the view model.store
saves theCancellable
in thedisposeBag
这是视图代码(为演示起见,未加载):
Here's the view code (without the loading, for the sake of the demo):
struct ContentView: View {
@ObservedObject var viewModel = GameListViewModel()
var body: some View {
NavigationView {
Group {
VStack {
SearchBar(text: $viewModel.searchTerm,
onSearchButtonClicked: viewModel.onSearchTapped)
List(viewModel.games, id: \.title) { game in
Text(verbatim: game.title)
}
}
}
.navigationBarTitle(Text("Games"))
}
}
}
搜索栏的实现:
struct SearchBar: UIViewRepresentable {
@Binding var text: String
var onSearchButtonClicked: (() -> Void)? = nil
class Coordinator: NSObject, UISearchBarDelegate {
let control: SearchBar
init(_ control: SearchBar) {
self.control = control
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
control.text = searchText
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
control.onSearchButtonClicked?()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
}
}
这篇关于iOS SwiftUI搜索栏和REST-API的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!