我在视图中有一个搜索栏,可以在其中进行搜索,搜索将传递给RESTapi,结果将显示在tableView上。以下是我的不同类别
型号:
struct MovieResponse: Codable {
var totalResults: Int
var response: String
var error: String
var movies: [Movie]
enum ConfigKeys: String, CodingKey {
case totalResults
case response = "Response"
case error = "Error"
case movies
}
init(totalResults: Int, response: String, error: String, movies: [Movie]) {
self.totalResults = totalResults
self.response = response
self.error = error
self.movies = movies
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.totalResults = try values.decodeIfPresent(Int.self, forKey: .totalResults) ?? 0
self.response = try values.decodeIfPresent(String.self, forKey: .response) ?? "False"
self.error = try values.decodeIfPresent(String.self, forKey: .error) ?? ""
self.movies = try values.decodeIfPresent([Movie].self, forKey: .movies) ?? []
}
}
extension MovieResponse {
struct Movie: Codable, Identifiable {
var id = UUID()
var title: String
var year: Int8
var imdbID: String
var type: String
var poster: URL
enum EncodingKeys: String, CodingKey {
case title = "Title"
case year = "Year"
case imdmID
case type = "Type"
case poster = "Poster"
}
}
}
ViewModel:
final class MovieListViewModel: ObservableObject {
@Published var isLoading: Bool = false
@Published var movieObj = MovieResponse(totalResults: 0, response: "False", error: "", movies: [])
var searchTerm: String = ""
private let searchTappedSubject = PassthroughSubject<Void, Error>()
private var disposeBag = Set<AnyCancellable>()
private let service = OMDBService()
init() {
searchTappedSubject
.flatMap {
self.requestMovies(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: .movieObj.movies, on: self)
.store(in: &disposeBag)
}
func onSearchTapped() {
searchTappedSubject.send(())
}
private func requestMovies(searchTerm: String) -> AnyPublisher<[MovieResponse.Movie], Error> {
guard let url = URL(string:"(Constants.HostName)/?s=(searchTerm)&apikey=(Constants.APIKey)") else {
fatalError("Something is wrong with URL")
}
return URLSession.shared.dataTaskPublisher(for: url)
.tryMap() { element -> Data in
guard let httpResponse = element.response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return element.data
}
.mapError { $0 as Error }
.decode(type: [MovieResponse.Movie].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
}
最后;搜索栏
struct SearchView: View {
@ObservedObject var viewModel = MovieListViewModel()
@State private var searchText = ""
var body: some View {
ZStack {
VStack {
HStack {
Text("Search OMDB")
.font(.system(size: 25, weight: .black, design: .rounded))
Spacer()
}
.padding()
Spacer()
SearchBar(text: $viewModel.searchTerm,
onSearchButtonClicked: viewModel.onSearchTapped)
List(viewModel.movieObj.movies) { movie in
Text(verbatim: movie.title)
}
.onAppear() {
print("Got the new data")
}
}
}
}
}
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
}
}
当我运行代码时,RESTapi正在返回数据,但我在Movie
数组中看不到相同的数据,List
也没有显示任何内容。
编辑:添加REST API 返回的示例json
{
"Search": [
{
"Title": "What We Do in the Shadows",
"Year": "2014",
"imdbID": "tt3416742",
"Type": "movie",
"Poster": "https://m.media-amazon.com/images/M/MV5BMjAwNDA5NzEwM15BMl5BanBnXkFtZTgwMTA1MDUyNDE@._V1_SX300.jpg"
},
{
"Title": "I Know What You Did Last Summer",
"Year": "1997",
"imdbID": "tt0119345",
"Type": "movie",
"Poster": "https://m.media-amazon.com/images/M/MV5BZDI4ODJlNGUtNjFiMy00ODgzLWIzYjgtMzgyZTljZDQ2NGZiXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"
}
],
"totalResults": "4365",
"Response": "True"
}
您的代码很少有问题。
- 主要是,您试图解码的类型不正确。数据是一个字典,可以解码为
MovieResponse
,但您正在尝试解码为[MovieResponse.Movie]
。这将失败
您可以通过添加轻松找到解码/网络错误
.mapError({ (error) -> Error in
print("error -- (error)")
return error
})
要修复此更改
.decode(type: [MovieResponse.Movie].self, decoder: JSONDecoder())
至
.decode(type: MovieResponse.self, decoder: JSONDecoder())
.map(.movies)
并且再次与编码密钥有一些不匹配,对于MovieResponse
- 修复打字错误
ConfigKeys
到CodingKeys
movies
数组应该从Search
对象中解码totalResults
应该是一个字符串
对于Movie
- 将
EncodingKeys
更改为CodingKeys
- 修复打字错误
imdmID
到imdbID
- 将
year
的类型更改为String
或使用自定义解码这些更改将修复映射问题
提示:在发布问题之前,至少要检查你的拼写错误;(