Swift - JSONDecoder & Combine 框架问题



我在视图中有一个搜索栏,可以在其中进行搜索,搜索将传递给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"
}

您的代码很少有问题。

  1. 主要是,您试图解码的类型不正确。数据是一个字典,可以解码为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

  1. 修复打字错误ConfigKeysCodingKeys
  2. movies数组应该从Search对象中解码
  3. totalResults应该是一个字符串

对于Movie

  1. EncodingKeys更改为CodingKeys
  2. 修复打字错误imdmIDimdbID
  3. year的类型更改为String或使用自定义解码这些更改将修复映射问题

提示:在发布问题之前,至少要检查你的拼写错误;(

相关内容

  • 没有找到相关文章

最新更新