I’ve tried completely different strategy to get the anticipated end result however it actually not working. Right here is the use case . Once I choose the desk view cell and I’m anticipating to indicate chosen cell particulars into particulars view controller and on the identical time making API name to get the associated information . The associated information id (Film ID) will come kind desk view cell. I’m making an attempt to mix each view content material as youngster view.
Right here is the did choose operate code ..
// MARK: - UITableViewControllerDelegate
extension MoviesViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let film = viewModel.state.motion pictures[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 information.**
self.navigationController?.pushViewController(viewController, animated: true)
}
}
Right here is the view mannequin code get the end result and retailer into native variable .. Right here func fetchSimilarMovie()
making API name and get the info 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
}
}
}
ultimate 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] lead to
guard let self = self else { return }
swap end result {
case .success(let movieDetails):
self.state = .loaded(movieDetails)
case .failure:
self.state = .error
}
}
}
func fetchSimilarMovie() {
apiManager.execute(Film.similiar(for: initialMovie.id)) { [weak self] lead to
guard let self = self else { return }
swap end result {
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 completely 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 ..
ultimate 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 applied")
}
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.predominant.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,
length: 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,
length: 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..
ultimate 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 applied")
}
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(prime: 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 applied")
}
fileprivate let collectionView:UICollectionView = {
let structure = UICollectionViewFlowLayout()
structure.scrollDirection = .horizontal
let cv = UICollectionView(body: .zero, collectionViewLayout: structure)
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.predominant.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, structure collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.body.width/2.5, peak: collectionView.body.width/2)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection part: Int) -> Int {
let gadgets = viewModel.moviePage.rely
return gadgets
}
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.
ultimate class MovieCell: UITableViewCell {
let columnSpacing: CGFloat = 16
let posterSize = CGSize(width: 92, peak: 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(prime: 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 = .prime
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 end result after I run the app..
Right here is the screenshot after I choose the cell .. I’m anticipating to indicate the small print of cell and present the same motion pictures particulars however its nil.
Right here is the end result after I choose completely different cell .. It exhibiting empty assortment view no information.