如何为TMDB多搜索API创建混合类型数据模型?
解决TMDB多类型搜索结果的统一模型适配与TableView展示问题
我来帮你搞定这个TMDB GET /search/multi 接口的混合结果适配问题,按照下面的步骤来就能轻松实现统一模型,并且在TableView里完美展示电影、剧集和演员三种类型的搜索结果:
1. 先定义媒体类型枚举
首先创建一个枚举来对应接口返回的media_type字段,这样我们能快速区分当前结果是哪种类型:
enum MediaType: String, Codable { case movie, tv, person }
2. 创建统一的搜索结果枚举模型
用枚举的关联值特性来包裹你已有的Movie模型,以及需要补充的TVShow和Person模型。这样一个SearchResult枚举值就能代表任意一种类型的搜索结果,完美适配接口返回的混合数组:
enum SearchResult: Codable, Equatable { case movie(Movie) case tvShow(TVShow) case person(Person) // 自定义解码逻辑:根据media_type判断类型,解码对应模型 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let mediaType = try container.decode(MediaType.self, forKey: .mediaType) switch mediaType { case .movie: let movie = try Movie(from: decoder) self = .movie(movie) case .tv: let tvShow = try TVShow(from: decoder) self = .tvShow(tvShow) case .person: let person = try Person(from: decoder) self = .person(person) } } // 自定义编码逻辑(如果需要上传数据的话可选实现) func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .movie(let movie): try container.encode(MediaType.movie, forKey: .mediaType) try movie.encode(to: encoder) case .tvShow(let tvShow): try container.encode(MediaType.tv, forKey: .mediaType) try tvShow.encode(to: encoder) case .person(let person): try container.encode(MediaType.person, forKey: .mediaType) try person.encode(to: encoder) } } enum CodingKeys: String, CodingKey { case mediaType = "media_type" } }
3. 补充TVShow和Person模型
你已经有了Movie模型,现在需要补充剧集和演员的模型,对应TMDB接口返回的字段:
TVShow模型示例
struct TVShow: Codable, Equatable { let posterPath: String? let originalName: String let firstAirDate: String let genreIds: [Int] let id: Int // 其他你需要的字段... // 提取播出年份,方便展示 var firstAirYear: String { guard !firstAirDate.isEmpty else { return "" } return String(firstAirDate.prefix(4)) } enum CodingKeys: String, CodingKey { case posterPath = "poster_path" case originalName = "original_name" case firstAirDate = "first_air_date" case genreIds = "genre_ids" case id // 其他字段的映射... } }
Person模型示例
struct Person: Codable, Equatable { let profilePath: String? let name: String let knownForDepartment: String let id: Int // 其他你需要的字段... enum CodingKeys: String, CodingKey { case profilePath = "profile_path" case name case knownForDepartment = "known_for_department" case id // 其他字段的映射... } }
4. 更新根搜索结果模型
把原来的MovieResults改成适配混合结果的MultiSearchResults:
struct MultiSearchResults: Codable { let page: Int let results: [SearchResult] let totalPages: Int let totalResults: Int enum CodingKeys: String, CodingKey { case page case results case totalPages = "total_pages" case totalResults = "total_results" } }
5. TableView数据源适配
现在就可以在TableView里根据SearchResult的类型,展示不同样式的单元格了:
// 数据源数组,存储所有搜索结果 var searchResults: [SearchResult] = [] // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return searchResults.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let result = searchResults[indexPath.row] switch result { case .movie(let movie): let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath) as! MovieCell // 配置电影单元格UI cell.titleLabel.text = movie.title cell.yearLabel.text = movie.releaseYear // 加载海报(这里用Kingfisher做图片加载,你也可以用其他库) if let posterPath = movie.posterPath { let posterURL = URL(string: "https://image.tmdb.org/t/p/w200\(posterPath)") cell.posterImageView.kf.setImage(with: posterURL) } return cell case .tvShow(let tvShow): let cell = tableView.dequeueReusableCell(withIdentifier: "TVShowCell", for: indexPath) as! TVShowCell // 配置剧集单元格UI cell.titleLabel.text = tvShow.originalName cell.yearLabel.text = tvShow.firstAirYear if let posterPath = tvShow.posterPath { let posterURL = URL(string: "https://image.tmdb.org/t/p/w200\(posterPath)") cell.posterImageView.kf.setImage(with: posterURL) } return cell case .person(let person): let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell", for: indexPath) as! PersonCell // 配置演员单元格UI cell.nameLabel.text = person.name cell.departmentLabel.text = person.knownForDepartment if let profilePath = person.profilePath { let profileURL = URL(string: "https://image.tmdb.org/t/p/w200\(profilePath)") cell.profileImageView.kf.setImage(with: profileURL) } return cell } }
最后补充说明
- 记得提前在Storyboard或代码里创建对应的
MovieCell、TVShowCell、PersonCell,根据各自的UI需求设计布局。 - 图片加载部分可以用你熟悉的第三方库(比如Kingfisher、SDWebImage),或者自己实现网络请求加载。
内容的提问来源于stack exchange,提问作者Zhou Haibo




