我想根据从表视图 didSelectRow 函数传递到集合视图的电影 ID 显示类似的电影。这是获取类似电影记录的 URL。 https://api.themoviedb.org/3/movie/129/similar?api_key=apikey。在这里,我传递电影 ID 129,它会获取类似的电影。我可以在调试控制台中看到我得到了正确的响应,但它没有在集合视图中显示类似的图像。它的代码有点多,因为我遵循编程方式来创建视图。
这是模型代码..这是通用结构,我在这里传递解码[电影]的电影。
struct Page<T: Decodable>: Decodable {
let pageNumber: Int
let totalResults: Int
let totalPages: Int
let results: [T]
enum CodingKeys: String, CodingKey {
case pageNumber = "page"
case totalResults = "total_results"
case totalPages = "total_pages"
case results
}
}
这是 URL 网络管理器类..
final class APIManager: APIManaging {
static let shared = APIManager()
let host = "https://api.themoviedb.org/3"
let apiKey = "apikey"
private let urlSession: URLSession
init(urlSession: URLSession = .shared) {
self.urlSession = urlSession
}
func execute<Value: Decodable>(_ request: Request<Value>, completion: @escaping (Result<Value, APIError>) -> Void) {
urlSession.dataTask(with: urlRequest(for: request)) { responseData, response, error in
if let data = responseData {
let response: Value
do {
response = try JSONDecoder().decode(Value.self, from: data)
} catch {
completion(.failure(.parsingError))
print(error)
return
}
completion(.success(response))
} else {
completion(.failure(.networkError))
}
}.resume()
}
private func urlRequest<Value>(for request: Request<Value>) -> URLRequest {
let url = URL(host, apiKey, request)
var result = URLRequest(url: url)
result.httpMethod = request.method.rawValue
result.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
return result
}
}
这里的函数被称为表单视图模型..
static func similiar(for movieID: Int) -> Request<Page<Movie>> {
return Request(method: .get, path: "movie/\(movieID)/similar")
}
这里是表视图的didSelect函数。在这里,我选择单元格并导航到详细信息视图..
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let movie = viewModel.state.movies[indexPath.row]
let viewModel = MoviesDetailsViewModel(movie: movie, apiManager: APIManager())
let viewController = MovieDetailsViewController(viewModel: viewModel)
viewModel.fetchSimilarMovie(for: indexPath.row)
self.navigationController?.pushViewController(viewController, animated: true)
}
这是通用视图控制器的代码..
class CommonViewController: UIViewController {
private let viewModel: MoviesDetailsViewModel
private var viewController : UIViewController!
init(viewModel: MoviesDetailsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
navigationItem.largeTitleDisplayMode = .never
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
combineView()
navigationItem.leftBarButtonItem = UIBarButtonItem.backButton(target: self, action: #selector(didTapBack(_:)))
}
private func combineView() {
viewController = CommonViewController(viewModel: viewModel)
let dvc = MovieDetailsViewController(viewModel: viewModel)
let svc = SmiliarMovieViewController(viewModel: viewModel)
viewController.addChild(dvc)
viewController.addChild(svc)
}
@objc private func didTapBack(_ sender: UIBarButtonItem) {
navigationController?.popViewController(animated: true)
}
}
这是视图模型类..
enum MoviesDetailsViewModelState {
case loading(Movie)
case loaded(MovieDetails)
case pageLoaded(Page<Movie>)
case error
/// rest of the enum here for movie and error..
var page: Page<Movie>? {
switch self {
case .loading, .error, .loaded:
return nil
case .pageLoaded(let page):
return page
}
}
}
final class MoviesDetailsViewModel {
private let apiManager: APIManaging
private let initialMovie: Movie
var moviePage = [Movie]()
init(movie: Movie, apiManager: APIManaging = APIManager()) {
self.initialMovie = movie
self.apiManager = apiManager
self.state = .loading(movie)
}
var updatedState: (() -> Void)?
var state: MoviesDetailsViewModelState {
didSet {
updatedState?()
}
}
func fetchData() {
apiManager.execute(MovieDetails.details(for: initialMovie)) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let movieDetails):
self.state = .loaded(movieDetails)
case .failure:
self.state = .error
}
}
}
func fetchSimilarMovie() {
apiManager.execute(Movie.similiar(for: initialMovie.id)) { [weak self] result in
guard let self = self else { return }
switch result {
case.success(let page):
self.state = .pageLoaded(page)
self.moviePage = page.results
case .failure(let error):
self.state = .error
print(error)
}
}
}
}
这是我的集合视图代码。现在我正在尝试显示电影图像。
import UIKit
class SmiliarMovieViewController: UIViewController {
private let viewModel: MoviesDetailsViewModel
init(viewModel: MoviesDetailsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
navigationItem.largeTitleDisplayMode = .never
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let width = UIScreen.main.bounds.width
layout.itemSize = CGSize(width: width, height: 80)
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.translatesAutoresizingMaskIntoConstraints = false
collection.dataSource = self
// collection.delegate = self
collection.register(SimilierMovieCell.self, forCellWithReuseIdentifier: SimilierMovieCell.identifier)
collection.backgroundColor = .lightGray
return collection
}()
private lazy var movieTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .black
label.numberOfLines = 0
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
viewModel.fetchSimilarMovie()
collectionView.reloadData()
}
private func setUpUI() {
self.view.addSubview(collectionView)
// constraints
let safeArea = view.safeAreaLayoutGuide
collectionView.topAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
collectionView.heightAnchor.constraint(equalToConstant: 80).isActive = true
}
}
extension SmiliarMovieViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let items = viewModel.moviePage.count
return items
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SimilierMovieCell.identifier, for: indexPath) as? SimilierMovieCell
let listMovie = viewModel.moviePage[indexPath.row]
print(listMovie)
cell?.configure(listMovie)
return cell ?? SimilierMovieCell()
}
}
这是手机号码。
class SimilierMovieCell: UICollectionViewCell {
static let identifier = "CompanyCell"
private lazy var companyImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
func configure(movieDetails: Movie) {
contentView.addSubview(companyImageView)
if let path = movieDetails.posterPath {
companyImageView.dm_setImage(posterPath: path)
} else {
companyImageView.image = nil
}
let safeArea = contentView.safeAreaLayoutGuide
companyImageView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
companyImageView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
companyImageView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
companyImageView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}
}
这里是下载图片的功能..
func dm_setImage(posterPath: String) {
let imageURL = URL(string: "https://image.tmdb.org/t/p/w185/\(posterPath)")
DispatchQueue.global().async {
let data = try? Data(contentsOf: imageURL!)
DispatchQueue.main.async {
guard let data = data else { return }
self.image = UIImage(data: data)
}
}
}
这是结果的截图。当我选择 tableview didselect 并在此处传递电影 ID 表单时,我希望在视图底部显示类似的电影详细信息,现在它是空的。
fetchsimilarMovie()
内部MoviesDetailsViewModel
调用apiManager内部定义的异步方法。异步执行不会阻塞代码,也不保证得到结果的时间。因此,在获得结果后必须添加逻辑来更新 UI。
在您的例子中,您在收到
MoviesDetailsViewModel
函数的结果后编写了代码来更改 apiManager.execute
的状态。但是,您没有设置 updatedState
闭包应该做什么。您需要将其设置为 collectionView.reloadData()
应该执行的操作,而不是仅在 viewDidLoad()
的 SmiliarMovieViewController
内调用 updatedState
。
class SmiliarMovieViewController: UIViewController {
private let viewModel: MoviesDetailsViewModel
init(viewModel: MoviesDetailsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
navigationItem.largeTitleDisplayMode = .never
}
//...
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
// Set `updatedState`
self.viewModel.updatedState = { [weak self] in
self.collectionView.reloadData()
}
viewModel.fetchSimilarMovie()
}
希望这能达到你想要的效果。