iOS SwiftUI 搜索栏和 REST-API
Posted
技术标签:
【中文标题】iOS SwiftUI 搜索栏和 REST-API【英文标题】:iOS SwiftUI Searchbar and REST-API 【发布时间】:2019-09-19 13:47:47 【问题描述】:我正在试验 SwiftUI,并希望使用搜索字符串从我的 REST API 中获取更新。
但是,我现在不确定如何将这两个组件组合在一起。
我希望你有一个想法。
这是我的代码:
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
【问题讨论】:
【参考方案1】:搜索文本应该在视图模型内。
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
,都会触发新游戏的请求。
这里发生了很多事情 - 让我们从 requestGames
开始。
我正在使用 JSONPlaceholder 免费 API 来获取一些数据并将其显示在列表中。
requestGames
执行网络请求,从收到的Data
解码[Game]
。除此之外,返回的数组会使用搜索字符串进行过滤(由于免费 API 的限制 - 在现实世界的场景中,您会在请求 URL 中使用查询参数)。
现在让我们看看视图模型构造函数。
事件顺序为:
获取“搜索点击”主题。 执行网络请求 (flatMap
)
在flatMap
内部,处理加载逻辑(在主队列上调度,因为isLoading
使用下面的Publisher
,如果在后台线程上发布值会出现警告)。
replaceError
将发布者的错误类型更改为 Never
,这是 assign
运算符的要求。
receiveOn
是必要的,因为由于网络请求,我们可能仍在后台队列中 - 我们希望在主队列上发布结果。
assign
更新视图模型上的数组 games
。
store
将Cancellable
保存在disposeBag
中
这是视图代码(为了演示,没有加载):
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
【讨论】:
谢谢。这可行,我对其进行了调整,以便将参数附加到请求中。但是现在视图在键入时会永久重新加载,因此我必须在搜索字段中一次又一次地单击。当我点击“发送”按钮时,我如何重写它以重新加载它(仅)? @Flolle 更新了我的答案以及搜索栏的实现(在答案的底部找到它)。 你太棒了——非常感谢你!但是我还有很多东西要学,必须仔细研究你的代码。新的 Swift 机制对我来说很不寻常。 很高兴能帮上忙。开始的最佳方式是观看 WWDC 视频 - 尤其是有关 SwiftUI 和 Combine 框架的视频。【参考方案2】:不需要涉及 UIKit,你可以像这样声明一个简单的搜索栏:
struct SearchBar: View
@State var searchString: String = ""
var body: some View
HStack
TextField("Start typing",
text: $searchString,
onCommit: self.performSearch() )
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: self.performSearch() )
Image(systemName: "magnifyingglass")
.padding()
func performSearch()
然后将搜索逻辑放在 performSearch() 中。
【讨论】:
被低估的评论,+1以上是关于iOS SwiftUI 搜索栏和 REST-API的主要内容,如果未能解决你的问题,请参考以下文章
iOS 16 中 SwiftUI 4.0 Toolbar 自定义背景色以及 List 自动编辑操作的原生支持
iOS 16 中 SwiftUI 4.0 Toolbar 自定义背景色以及 List 自动编辑操作的原生支持
iOS 16 中 SwiftUI 4.0 Toolbar 自定义背景色以及 List 自动编辑操作的原生支持