0

I have UICollectionView in UITableViewCell (Autolayout programmatically) and I'm having a problem with fetching API data and putting the result in a UITableView, the API should be called when the UICollectionViewCell clicked. But, there is no result from the API.

I tried to debug but I didn't get the problem.

HomeView:

protocol HomeViewDidSelectActionDelegate: class {
    func homeView(_ view: HomeView, didSelectCategoryWithTitle title: String)
}

class HomeView: UIView {

    var recipes: Recipes?
    var recipesDetails = [Recipe]()
    let indicator = ActivityIndicator()

    weak var homeViewDidSelectActionDelegate: HomeViewDidSelectActionDelegate?

    override init( frame: CGRect) {
        super.init(frame: frame)
        layoutUI()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    lazy var foodTableView: UITableView = {
        let foodTableView = UITableView()
        foodTableView.translatesAutoresizingMaskIntoConstraints = false
        foodTableView.backgroundColor = #colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1)
        foodTableView.delegate = self
        foodTableView.dataSource = self
        foodTableView.register(CategoriesTableViewCellCollectionViewCell.self, forCellReuseIdentifier: "CategoriesTableViewCellCollectionViewCell")
        foodTableView.register(PopularRecipesTableViewCellCollectionViewCell.self, forCellReuseIdentifier: "PopularRecipesTableViewCellCollectionViewCell")
        foodTableView.register(HomeTableViewCell.self, forCellReuseIdentifier: "HomeTableViewCell")
        foodTableView.rowHeight = UITableView.automaticDimension
        foodTableView.estimatedRowHeight = 100
        foodTableView.showsVerticalScrollIndicator = false
        foodTableView.separatorStyle = .none
        return foodTableView
    }()

    func setupFoodTableView() {

        NSLayoutConstraint.activate([
            foodTableView.topAnchor.constraint(equalTo: topAnchor),
            foodTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
            foodTableView.leadingAnchor.constraint(equalTo: leadingAnchor),
            foodTableView.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])
    }

    func addSubview() {
        addSubview(foodTableView)
    }

    func layoutUI() {
        indicator.setupIndicatorView(self, containerColor: .customDarkGray(), indicatorColor: .white)
        addSubview()
        setupFoodTableView()
        DispatchQueue.main.async {
            self.fetchData()
        }

    }

    func fetchData() {
        AF.request("https://apilink.com").responseJSON { (response) in
            if let error = response.error {
                print(error)
            }
            do {
                if let data = response.data {
                    self.recipes = try JSONDecoder().decode(Recipes.self, from: data)
                    self.recipesDetails = self.recipes?.recipes ?? []
                    DispatchQueue.main.async {
                        self.foodTableView.reloadData()
                    }
                }

            } catch {
                print(error)
            }
            self.indicator.hideIndicatorView(self)
        }
    }

}

extension HomeView: UITableViewDelegate, UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 3
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            return 1
        } else if section == 1 {
            return 1
        } else {
            return recipesDetails.count
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if indexPath.section == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CategoriesTableViewCellCollectionViewCell", for: indexPath) as! CategoriesTableViewCellCollectionViewCell
            cell.recipesDidselectActionDelegate = self
            cell.collectionView.reloadData()
            return cell
        } else if indexPath.section == 1 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "PopularRecipesTableViewCellCollectionViewCell", for: indexPath) as! PopularRecipesTableViewCellCollectionViewCell
            cell.collectionView.reloadData()
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
            let url = URL(string: recipesDetails[indexPath.row].image ?? "Error")
            cell.foodImage.kf.setImage(with: url)
            cell.foodTitle.text = recipesDetails[indexPath.row].title

            if let readyInMin = recipesDetails[indexPath.row].readyInMinutes {
                cell.cookingTimeInfoLabel.text = "\(readyInMin) Minutes"
            }

            if let pricePerServing = recipesDetails[indexPath.row].pricePerServing {
                cell.priceInfoLabel.text = "$\(Int(pricePerServing))"
            }

            if let serving = recipesDetails[indexPath.row].servings {
                cell.servesInfoLabel.text = "\(serving)"
            }

            return cell
        }

    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 2 {
            return "Random recipes"
        } else {
            return ""
        }
    }

    func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
        (view as! UITableViewHeaderFooterView).contentView.backgroundColor = #colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1)
        (view as! UITableViewHeaderFooterView).textLabel?.font = UIFont(name: "AvenirNext-DemiBold", size: 16)
        (view as! UITableViewHeaderFooterView).textLabel?.textColor = .customDarkGray()
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 30.0
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.section == 0 {
            return 130
        } else if indexPath.section == 1 {
            return 180
        } else {
            return UITableView.automaticDimension
        }

    }

}

extension HomeView: RecipesDidselectActionDelegate {

    func categoriesTableViewCell(_ cell: UICollectionView, didSelectTitle title: String) {
        homeViewDidSelectActionDelegate?.homeView(self, didSelectCategoryWithTitle: title)
    }
}

HomeViewController:

class HomeViewController: UIViewController {

    let searchController = UISearchController(searchResultsController: nil)
    let leftMenuNavigationController = SideMenuNavigationController(rootViewController: SideMenuTableViewController())

    lazy var mainView: HomeView = {
        let view = HomeView(frame: self.view.frame)
        view.homeViewDidSelectActionDelegate = self
        return view
    }()

    override func loadView() {
        super.loadView()
        view = mainView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override var preferredStatusBarStyle: UIStatusBarStyle {
        .lightContent
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.isNavigationBarHidden = false
        setNeedsStatusBarAppearanceUpdate()
        setupNavigationWithLargeTitle()
        setupLeftSideMenu()
        setupNavigation()
    }

    func setupLeftSideMenu() {
        SideMenuManager.default.leftMenuNavigationController = leftMenuNavigationController
        leftMenuNavigationController.leftSide = true
        leftMenuNavigationController.statusBarEndAlpha = 0
        leftMenuNavigationController.presentationStyle = .viewSlideOut
        leftMenuNavigationController.allowPushOfSameClassTwice = false
        leftMenuNavigationController.menuWidth = view.frame.width * (3/4)
        leftMenuNavigationController.navigationBar.isHidden = true
    }

}

extension HomeViewController: UISearchControllerDelegate, UISearchBarDelegate {
    func setupNavigationWithLargeTitle() {
        navigationController?.navigationBar.prefersLargeTitles = true
        searchController.delegate = self
        searchController.searchBar.delegate = self
        searchController.searchBar.searchTextField.backgroundColor = .white
        searchController.searchBar.searchTextField.textColor = .customDarkGray()
        searchController.searchBar.searchTextField.font = UIFont(name: "AvenirNext-Regular", size: 14)
        searchController.searchBar.tintColor = UIColor.CustomGreen()
        self.navigationItem.searchController = searchController
        self.title = "Home"
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.CustomGreen()]
        navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.CustomGreen(), .font: UIFont(name: "AvenirNext-Heavy", size: 36)!]
        navigationController?.navigationBar.tintColor = .white
        navigationController?.navigationBar.standardAppearance = navBarAppearance
        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
        navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "heart.fill"), style: .plain, target: self, action: #selector(saveButtonTapped))
        navigationItem.rightBarButtonItem?.tintColor = UIColor.CustomGreen()
        navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(named: "menu"), style: .plain, target: self, action: #selector(menuButtonTapped))
        navigationItem.leftBarButtonItem?.tintColor = UIColor.CustomGreen()
    }

    @objc func saveButtonTapped() {
        print("OK")
    }

    @objc func menuButtonTapped() {
        self.present(leftMenuNavigationController, animated: true, completion: nil)
    }
}

extension HomeViewController: HomeViewDidSelectActionDelegate {

    func homeView(_ view: HomeView, didSelectCategoryWithTitle title: String) {
        let vc = RecipesTableViewDetails()
        vc.categoryTitle = title
        self.show(vc, sender: nil)
    }
}

CollectionView:

protocol RecipesDidselectActionDelegate: class {
    func categoriesTableViewCell(_ cell: UICollectionView, didSelectTitle title: String)
}

class CategoriesTableViewCellCollectionViewCell: UITableViewCell, UICollectionViewDelegateFlowLayout {

    weak var recipesDidselectActionDelegate: RecipesDidselectActionDelegate?

    let categories: [String] = [
        "Main course",
        "Beef",
        "Chicken",
        "Seafood",
        "Vegetarian",
        "Breakfast",
        "Side dish",
        "Drink",
        "Sauce",
        "Soup",
        "Snacks",
        "Dessert"
    ]

    let categoriesTitle: [String] = [
        "maincourse",
        "beef",
        "chicken",
        "seafood",
        "vegetarian",
        "breakfast",
        "sidedish",
        "drink",
        "sauce",
        "soup",
        "snacks",
        "dessert"
    ]

    let categoriesImages: [UIImage] = [
        UIImage(named: "maincourse")!,
        UIImage(named: "beef")!,
        UIImage(named: "chicken")!,
        UIImage(named: "seafood")!,
        UIImage(named: "vegetarian")!,
        UIImage(named: "breakfast")!,
        UIImage(named: "sidedish")!,
        UIImage(named: "drink")!,
        UIImage(named: "sauce")!,
        UIImage(named: "soup")!,
        UIImage(named: "snacks")!,
        UIImage(named: "dessert")!
    ]

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        layoutUI()
        selectionStyle = .none
        self.backgroundColor = .clear
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    lazy var containerView: UIView = {
        let containerView = UIView()
        containerView.backgroundColor = .clear
        containerView.translatesAutoresizingMaskIntoConstraints = false
        return containerView
    }()

    lazy var categoriesNameLabel: UILabel = {
        let categoriesNameLabel = UILabel()
        categoriesNameLabel.text = "Categories"
        categoriesNameLabel.textColor = .customDarkGray()
        categoriesNameLabel.textAlignment = .left
        categoriesNameLabel.font = UIFont(name: "AvenirNext-DemiBold", size: 16)
        categoriesNameLabel.translatesAutoresizingMaskIntoConstraints = false
        return categoriesNameLabel
    }()

    lazy var seeAllCategoriesButton: UIButton = {
        let seeAllCategoriesButton = UIButton()
        seeAllCategoriesButton.setTitle("See all", for: .normal)
        seeAllCategoriesButton.setTitleColor(.CustomGreen(), for: .normal)
        seeAllCategoriesButton.titleLabel?.font = UIFont(name: "AvenirNext-Regular", size: 14)
        seeAllCategoriesButton.translatesAutoresizingMaskIntoConstraints = false
        seeAllCategoriesButton.addTarget(self, action: #selector(test), for: .touchUpInside)
        return seeAllCategoriesButton
    }()

    @objc func test() {
        print("Test worked")
    }

    lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.backgroundColor = .clear
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(CategoriesCollectionViewCell.self, forCellWithReuseIdentifier: "CategoriesCollectionViewCell")
        return collectionView
    }()

    func setupContainerViewConstraints() {
        NSLayoutConstraint.activate([
            containerView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
            containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0)
        ])
    }

    func setupCategoriesNameLabelConstraints() {
        NSLayoutConstraint.activate([
            categoriesNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            categoriesNameLabel.centerYAnchor.constraint(equalTo: seeAllCategoriesButton.centerYAnchor)
        ])
    }

    func setupSeeAllCategoriesButtonConstraints() {
        NSLayoutConstraint.activate([
            seeAllCategoriesButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            seeAllCategoriesButton.topAnchor.constraint(equalTo: containerView.topAnchor)
        ])
    }

    func setupCollectionViewConstraints() {
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: seeAllCategoriesButton.bottomAnchor, constant: 0),
            collectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0),
            collectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            collectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
        ])
    }

    func addSubviews() {
        addSubview(containerView)
        containerView.addSubview(categoriesNameLabel)
        containerView.addSubview(seeAllCategoriesButton)
        containerView.addSubview(collectionView)
    }

    func layoutUI() {
        addSubviews()
        setupCollectionViewConstraints()
        setupContainerViewConstraints()
        setupCategoriesNameLabelConstraints()
        setupSeeAllCategoriesButtonConstraints()
    }

}

extension CategoriesTableViewCellCollectionViewCell: UICollectionViewDelegate, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return categories.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoriesCollectionViewCell", for: indexPath) as! CategoriesCollectionViewCell
        cell.categoriesImage.image = categoriesImages[indexPath.row]
        cell.categoryName.text = categories[indexPath.row]
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        recipesDidselectActionDelegate?.categoriesTableViewCell(collectionView, didSelectTitle: categoriesTitle[indexPath.row])

    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let w: CGFloat = self.frame.width * 0.4
        let h: CGFloat = collectionView.frame.size.height - 6.0
        return CGSize(width: w, height: h)
    }

}

RecipesTableViewDetailsView:

class RecipesTableViewDetailsView: UIView {

    let indicator = ActivityIndicator()
    let recipesTableVC = RecipesTableViewDetails()

    override init(frame: CGRect) {
        super.init(frame: frame)
        layoutUI()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    lazy var foodTableView: UITableView = {
        let foodTableView = UITableView()
        foodTableView.translatesAutoresizingMaskIntoConstraints = false
        foodTableView.backgroundColor = #colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1)
        foodTableView.delegate = self
        foodTableView.dataSource = self
        foodTableView.register(HomeTableViewCell.self, forCellReuseIdentifier: "HomeTableViewCell")
        foodTableView.rowHeight = UITableView.automaticDimension
        foodTableView.estimatedRowHeight = 100
        foodTableView.showsVerticalScrollIndicator = false
        foodTableView.separatorStyle = .none
        return foodTableView
    }()

    func setupFoodTableView() {

        NSLayoutConstraint.activate([
            foodTableView.topAnchor.constraint(equalTo: topAnchor),
            foodTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
            foodTableView.leadingAnchor.constraint(equalTo: leadingAnchor),
            foodTableView.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])
    }

    func addSubview() {
        addSubview(foodTableView)
    }

    func layoutUI() {
        addSubview()
        setupFoodTableView()
    }

}

extension RecipesTableViewDetailsView: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return recipesTableVC.recipesDetails.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
        let newURL = URL(string: recipesTableVC.recipesDetails[indexPath.row].image ?? "Error")
        cell.foodImage.kf.setImage(with: newURL)
        cell.foodTitle.text = recipesTableVC.recipesDetails[indexPath.row].title

        if let readyInMin = recipesTableVC.recipesDetails[indexPath.row].readyInMinutes {
            cell.cookingTimeInfoLabel.text = "\(readyInMin) Minutes"
        }

        if let pricePerServing = recipesTableVC.recipesDetails[indexPath.row].pricePerServing {
            cell.priceInfoLabel.text = "$\(Int(pricePerServing))"
        }

        if let serving = recipesTableVC.recipesDetails[indexPath.row].servings {
            cell.servesInfoLabel.text = "\(serving)"
        }

        return cell

    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }

}

RecipesTableViewDetailsViewController:

class RecipesTableViewDetails: UIViewController {


    var categoryTitle: String?
    var recipes: Recipes?
    var recipesDetails = [Recipe]()
    let indicator = ActivityIndicator()

    lazy var mainView: RecipesTableViewDetailsView = {
        let view = RecipesTableViewDetailsView(frame: self.view.frame)
        view.backgroundColor = .white
        return view
    }()

    override func loadView() {
        super.loadView()
        view = mainView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if let categoryTitle = categoryTitle {
            fetchData(categoryTitle)
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.isNavigationBarHidden = false
    }

    func fetchData(_ category: String) {
        indicator.setupIndicatorView(view, containerColor: .customDarkGray(), indicatorColor: .white)
        AF.request("https://api.link.com/recipes/random?number=25&tags=\(category)").responseJSON { (response) in
            if let error = response.error {
                print(error)
            }
            do {
                if let data = response.data {
                    self.recipes = try JSONDecoder().decode(Recipes.self, from: data)
                    self.recipesDetails = self.recipes?.recipes ?? []
                    DispatchQueue.main.async {
                        self.mainView.foodTableView.reloadData()
                    }
                }

            } catch {
                print(error)
            }
            self.indicator.hideIndicatorView(self.view)
        }
    }

}

Recipes:

// MARK: - Recipes
struct Recipes: Codable {
    let recipes: [Recipe]
}

// MARK: - Recipe
struct Recipe: Codable {
    let title: String?
    let image: String?
    let pricePerServing: Double?
    let readyInMinutes, servings: Int?
}
18
  • In fetchData function could you please print response.data? Commented Mar 13, 2020 at 9:56
  • Unable to run your code. Try giving the minimal code that runs. That way it's easier to debug and let you know the answer. Commented Mar 13, 2020 at 10:10
  • @Daljeet Unfortunately, there is no result from the API in my code. I'm using this API api.spoonacular.com/recipes/… Commented Mar 13, 2020 at 10:40
  • @FelixMarianayagam I'm sorry I didn't paste minimal code. I'm using this API: api.spoonacular.com/recipes/… Commented Mar 13, 2020 at 10:40
  • Why you are not adding apiKey in url in your code? Commented Mar 13, 2020 at 10:46

1 Answer 1

1

In your RecipesTableViewDetailsView class, you are creating a new instance of RecipesTableViewDetails:

let recipesTableVC = RecipesTableViewDetails()

However, in your RecipesTableViewDetails, you were setting the view to another new instance of RecipesTableViewDetails:

lazy var mainView: RecipesTableViewDetailsView = {
    let view = RecipesTableViewDetailsView(frame: self.view.frame)
    view.backgroundColor = .white
    return view
}()

So one instance is fetching the data, but your table is trying to use data from the other instance.

You can fix it by changing:

let recipesTableVC = RecipesTableViewDetails()

to:

var recipesTableVC: RecipesTableViewDetails!

and then in RecipesTableViewDetails assign that var:

override func loadView() {
    super.loadView()
    // assign here
    mainView.recipesTableVC = self
    view = mainView
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.