I’ve tried totally different strategy to get the anticipated outcome but it surely actually not working. Right here is the use case . After I choose the desk view cell and I’m anticipating to point out chosen cell particulars into particulars view controller and on the identical time making API name to get the associated knowledge . The associated knowledge id (Film ID) will come type desk view cell. I’m making an attempt to mix each view content material as youngster view.
Right here is the did choose perform code ..
// MARK: - UITableViewControllerDelegate
extension MoviesViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let film = viewModel.state.films[indexPath.row]
let viewModel = MoviesDetailsViewModel(film: film, apiManager: APIManager())
let viewController = MovieDetailsViewController(viewModel: viewModel)
viewModel.fetchSimilarMovie() **// right here making API for to get related film knowledge.**
self.navigationController?.pushViewController(viewController, animated: true)
}
}
Right here is the view mannequin code get the outcome and retailer into native variable .. Right here func fetchSimilarMovie()
making API name and get the information and retailer into var moviePage = [Movie]()
enum MoviesDetailsViewModelState {
case loading(Film)
case loaded(MovieDetails)
case pageLoaded(Web page<Film>)
case error
var title: String? {
swap self {
case .loaded(let film):
return film.title
case .loading(let film):
return film.title
case .error:
return nil
case .pageLoaded:
return nil
}
}
var film: MovieDetails? {
swap self {
case .loaded(let film):
return film
case .loading, .error:
return nil
case .pageLoaded:
return nil
}
}
var web page: Web page<Film>? {
swap self {
case .loading, .error, .loaded:
return nil
case .pageLoaded(let web page):
return web page
}
}
}
closing class MoviesDetailsViewModel {
personal let apiManager: APIManaging
personal let initialMovie: Film
var moviePage = [Movie]()
init(film: Film, apiManager: APIManaging = APIManager()) {
self.initialMovie = film
self.apiManager = apiManager
self.state = .loading(film)
}
var updatedState: (() -> Void)?
var state: MoviesDetailsViewModelState {
didSet {
updatedState?()
}
}
func fetchData() {
apiManager.execute(MovieDetails.particulars(for: initialMovie)) { [weak self] end in
guard let self = self else { return }
swap outcome {
case .success(let movieDetails):
self.state = .loaded(movieDetails)
case .failure:
self.state = .error
}
}
}
func fetchSimilarMovie() {
apiManager.execute(Film.similiar(for: initialMovie.id)) { [weak self] end in
guard let self = self else { return }
swap outcome {
case.success(let web page):
self.state = .pageLoaded(web page)
self.moviePage = web page.outcomes
print(moviePage)
case .failure(let error):
self.state = .error
print(error)
}
}
}
}
Right here is the MovieDetailsViewController the place I’ve totally different state to show the content material.. The personal func showMovieDetails(_ movieDetails: MovieDetails)
right here I’m creating container for 3 views and add it as youngster view ..
closing class MovieDetailsViewController: UIViewController {
personal let viewModel: MoviesDetailsViewModel
personal var currentViewController: UIViewController!
init(viewModel: MoviesDetailsViewModel) {
self.viewModel = viewModel
tremendous.init(nibName: nil, bundle: nil)
navigationItem.largeTitleDisplayMode = .by no means
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been carried out")
}
override func viewDidLoad() {
tremendous.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem.backButton(goal: self, motion: #selector(didTapBack(_:)))
updateFromViewModel()
bindViewModel()
viewModel.fetchData()
}
personal func bindViewModel() {
viewModel.updatedState = { [weak self] in
guard let self else { return }
DispatchQueue.essential.async {
self.updateFromViewModel()
}
}
}
personal func updateFromViewModel() {
let state = viewModel.state
title = state.title
swap state {
case .loading(let film):
self.showLoading(film)
case .loaded(let particulars):
self.showMovieDetails(particulars)
case .error:
self.showError()
case .pageLoaded(let web page):
self.showSimiliarMovieDetails(web page)
}
}
personal func showLoading(_ film: Film) {
let loadingViewController = LoadingViewController()
addChild(loadingViewController)
loadingViewController.view.body = view.bounds
loadingViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(loadingViewController.view)
loadingViewController.didMove(toParent: self)
currentViewController = loadingViewController
}
personal func showMovieDetails(_ movieDetails: MovieDetails) {
let containerView = UIViewController()
let displayViewController = MovieDetailsDisplayViewController(movieDetails: movieDetails)
let smiliarMovieViewController = SmiliarMovieViewController(viewModel: viewModel)
containerView.addChild(smiliarMovieViewController)
containerView.addChild(displayViewController)
let loadingViewController = LoadingViewController()
containerView.addChild(loadingViewController)
containerView.willMove(toParent: nil)
addChild(containerView)
transition(
from: currentViewController,
to: containerView,
period: 0.25,
choices: [.transitionCrossDissolve],
animations: nil
) { (_) in
self.currentViewController.removeFromParent()
self.currentViewController = containerView
self.currentViewController.didMove(toParent: self)
}
}
personal func showSimiliarMovieDetails(_ similiarMovieDetails: Web page<Film>) {
let smiliarMovieViewController = SmiliarMovieViewController(viewModel: viewModel)
addChild(smiliarMovieViewController)
smiliarMovieViewController.view.body = view.bounds
smiliarMovieViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
currentViewController?.willMove(toParent: nil)
transition(
from: currentViewController,
to: smiliarMovieViewController,
period: 0.25,
choices: [.transitionCrossDissolve],
animations: nil
) { (_) in
self.currentViewController.removeFromParent()
self.currentViewController = smiliarMovieViewController
self.currentViewController.didMove(toParent: self)
}
}
}
Right here is the code for MovieDetailsDisplayViewController. I’m following programmatic strategy to create the view..
closing class MovieDetailsDisplayViewController: UIViewController {
let movieDetails: MovieDetails
init(movieDetails: MovieDetails) {
self.movieDetails = movieDetails
tremendous.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been carried out")
}
override func loadView() {
view = ParentView()
}
override func viewDidLoad() {
tremendous.viewDidLoad()
view = ParentView()
(view as? ParentView)?.configure(movieDetails: movieDetails)
}
personal class ParentView: UIView {
let scrollView = UIScrollView()
let backdropImageView = UIImageView()
let titleLabel = UILabel()
let overviewLabel = UILabel()
let similarLabel = UILabel()
personal lazy var contentStackView = UIStackView(arrangedSubviews: [backdropImageView, titleLabel, overviewLabel, similarLabel])
override init(body: CGRect) {
tremendous.init(body: body)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder: aDecoder)
commonInit()
}
personal func commonInit() {
backgroundColor = .white
backdropImageView.contentMode = .scaleAspectFill
backdropImageView.clipsToBounds = true
titleLabel.font = UIFont.Heading.medium
titleLabel.textColor = UIColor.Textual content.charcoal
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.setContentHuggingPriority(.required, for: .vertical)
overviewLabel.font = UIFont.Physique.small
overviewLabel.textColor = UIColor.Textual content.gray
overviewLabel.numberOfLines = 0
overviewLabel.lineBreakMode = .byWordWrapping
similarLabel.font = UIFont.Physique.smallSemiBold
similarLabel.textColor = UIColor.Textual content.charcoal
similarLabel.numberOfLines = 0
similarLabel.lineBreakMode = .byWordWrapping
contentStackView.axis = .vertical
contentStackView.spacing = 24
contentStackView.setCustomSpacing(8, after: titleLabel)
setupViewsHierarchy()
setupConstraints()
}
personal func setupViewsHierarchy() {
addSubview(scrollView)
scrollView.addSubview(contentStackView)
}
personal func setupConstraints() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
backdropImageView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(
[
scrollView.topAnchor.constraint(equalTo: topAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 24),
contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -24)
]
)
scrollView.layoutMargins = UIEdgeInsets(high: 24, left: 16, backside: 24, proper: 16)
preservesSuperviewLayoutMargins = false
}
func configure(movieDetails: MovieDetails) {
backdropImageView.dm_setImage(backdropPath: movieDetails.backdropPath)
titleLabel.textual content = movieDetails.title
overviewLabel.textual content = movieDetails.overview
}
}
}
Right here is the code for SmiliarMovieViewController.
class SmiliarMovieViewController: UIViewController, UICollectionViewDelegate {
personal let viewModel: MoviesDetailsViewModel
init(viewModel: MoviesDetailsViewModel) {
self.viewModel = viewModel
tremendous.init(nibName: nil, bundle: nil)
navigationItem.largeTitleDisplayMode = .by no means
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been carried out")
}
fileprivate let collectionView:UICollectionView = {
let format = UICollectionViewFlowLayout()
format.scrollDirection = .horizontal
let cv = UICollectionView(body: .zero, collectionViewLayout: format)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(SimilierMovieCell.self, forCellWithReuseIdentifier: "CompanyCell")
cv.backgroundColor = .lightGray
return cv
}()
override func viewDidLoad() {
tremendous.viewDidLoad()
setUpUI()
self.viewModel.updatedState = {[weak self] in
DispatchQueue.essential.async {
self?.collectionView.reloadData()
}
}
viewModel.fetchSimilarMovie()
}
personal func setUpUI() {
view.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: view.topAnchor, fixed: 40).isActive = true
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, fixed: 40).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, fixed: -40).isActive = true
collectionView.heightAnchor.constraint(equalToConstant: view.body.width/2).isActive = true
}
}
extension SmiliarMovieViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, format collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.body.width/2.5, top: collectionView.body.width/2)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection part: Int) -> Int {
let objects = viewModel.moviePage.depend
return objects
}
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()
}
}
Right here is the cell code .. Similar code I’ve reused for assortment view cell.
closing class MovieCell: UITableViewCell {
let columnSpacing: CGFloat = 16
let posterSize = CGSize(width: 92, top: 134)
let coverImage = UIImageView()
let tagView = TagView()
let titleLabel = UILabel()
let descriptionLabel = UILabel()
let textStackView = UIStackView()
let imageStackView = UIStackView()
let containerStackView = UIStackView()
override init(fashion: UITableViewCell.CellStyle, reuseIdentifier: String?) {
tremendous.init(fashion: fashion, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder: aDecoder)
commonInit()
}
personal func commonInit() {
layoutMargins = UIEdgeInsets(high: 16, left: 16, backside: 16, proper: 16)
titleLabel.font = UIFont.Heading.small
titleLabel.textColor = UIColor.Textual content.charcoal
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
descriptionLabel.font = UIFont.Physique.small
descriptionLabel.textColor = UIColor.Textual content.gray
descriptionLabel.numberOfLines = 0
descriptionLabel.lineBreakMode = .byWordWrapping
coverImage.contentMode = .scaleAspectFit
coverImage.layer.cornerRadius = 8
coverImage.layer.masksToBounds = true
textStackView.spacing = 4
textStackView.alignment = .main
textStackView.axis = .vertical
imageStackView.spacing = 10
imageStackView.alignment = .main
imageStackView.axis = .vertical
containerStackView.spacing = columnSpacing
containerStackView.alignment = .high
containerStackView.translatesAutoresizingMaskIntoConstraints = false
setupViewsHierarchy()
setupConstraints()
}
func setupViewsHierarchy() {
contentView.addSubview(containerStackView)
imageStackView.dm_addArrangedSubviews(coverImage, tagView)
textStackView.dm_addArrangedSubviews(titleLabel, descriptionLabel)
containerStackView.dm_addArrangedSubviews(imageStackView, textStackView)
}
func setupConstraints() {
NSLayoutConstraint.activate([
containerStackView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
containerStackView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
containerStackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
containerStackView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
coverImage.widthAnchor.constraint(equalToConstant: posterSize.width),
coverImage.heightAnchor.constraint(equalToConstant: posterSize.height)
])
}
func configure(_ film: Film) {
titleLabel.textual content = film.title
descriptionLabel.textual content = film.overview
tagView.configure(.score(worth: film.voteAverage))
if let path = film.posterPath {
coverImage.dm_setImage(posterPath: path)
} else {
coverImage.picture = nil
}
}
override func prepareForReuse() {
tremendous.prepareForReuse()
}
}
Right here is the outcome after I run the app..
Right here is the screenshot after I choose the cell .. I’m anticipating to point out the small print of cell and present the same films particulars however its nil.
Right here is the outcome after I choose totally different cell .. It exhibiting empty assortment view no knowledge.