You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为TMDB多搜索API创建混合类型数据模型?

解决TMDB多类型搜索结果的统一模型适配与TableView展示问题

我来帮你搞定这个TMDB GET /search/multi 接口的混合结果适配问题,按照下面的步骤来就能轻松实现统一模型,并且在TableView里完美展示电影、剧集和演员三种类型的搜索结果:

1. 先定义媒体类型枚举

首先创建一个枚举来对应接口返回的media_type字段,这样我们能快速区分当前结果是哪种类型:

enum MediaType: String, Codable {
    case movie, tv, person
}

2. 创建统一的搜索结果枚举模型

用枚举的关联值特性来包裹你已有的Movie模型,以及需要补充的TVShowPerson模型。这样一个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或代码里创建对应的MovieCellTVShowCellPersonCell,根据各自的UI需求设计布局。
  • 图片加载部分可以用你熟悉的第三方库(比如Kingfisher、SDWebImage),或者自己实现网络请求加载。

内容的提问来源于stack exchange,提问作者Zhou Haibo

火山引擎 最新活动