skip to Main Content

I’m a beginner. I pull out pictures in cells through api. Everything is built, but instead of pictures – it’s empty. Returns nil. I’ve been sitting here all day and can’t figure it out!

API link – https://swiftbook.ru//wp-content/uploads/api/api_courses

If this answer is somewhere, I apologize, and if it’s not difficult to give a link, send it please, thank you.

Thank you very much in advance for your help and clarification!!!

enter image description here

import UIKit

class CourseCell: UITableViewCell {
    @IBOutlet var courseImage: UIImageView!
    @IBOutlet var courseNameLabel: UILabel!
    @IBOutlet var numberOfLessons: UILabel!
    @IBOutlet var numberOfTests: UILabel!
    
    func configure(with course: Course) {
        
        courseNameLabel.text = course.name
        numberOfLessons.text = "Number of lessons (course.number_of_lessons ?? 0)"
        numberOfTests.text = "Number of tests (course.number_of_tests ?? 0)"
        
        DispatchQueue.global().async {
            
            guard let stringUrl = course.imageUrl,
                  let imageURL = URL(string: stringUrl),
                  let imageData = try? Data(contentsOf: imageURL)
            else {
                return
            }
            
            DispatchQueue.main.async {
                self.courseImage.image = UIImage(data: imageData)
            }      
   
        }   
    }
}

Model for decode by JSON

Course.swift

struct Course: Decodable {
    let name: String?
    let imageUrl: String?
    let number_of_lessons: Int?
    let number_of_tests: Int?
}

struct WebsiteDescription: Decodable {
    let courses: [Course]?
    let websiteDescription: String?
    let websiteName: String?
}

And piece of code with JSON from CoursesViewController.swift

extension CoursesViewController {
    func fetchCourses() {
        guard let url = URL(string: URLExamples.exampleTwo.rawValue) else { return }
        
        URLSession.shared.dataTask(with: url) { (data, _, _) in
            guard let data = data else {
                return
            }
            
            do {
                // получаем курсы в переменную
                self.courses = try JSONDecoder().decode([Course].self, from: data)
                // и мы должны перезагрузить таблицу
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            } catch let error {
                print(error)
            }
        }.resume()
    }
}

And here is i get nil probably (please see screenshot below)

enter image description here

2

Answers


  1. Chosen as BEST ANSWER

    The answer above is Excellent !!! It's an additional valuable experience for me!

    But main reason was in blocked .ru domains in my country. WHEN I ACCIDENTALLY TURNED ON THE VPN ON THE MAC AND BUILD APP, THEN EVERYTHING LOADED!!! Because I am in Ukraine now, and we have all .ru domains blocked, and the API URL is just on .ru


  2. I tried to make another version of your code and it’s able to run. You can check my code and compare with your own.

    CoursesViewController

    class CoursesViewController: UIViewController {
        private lazy var tableView: UITableView = {
            let tableView = UITableView(frame: .zero, style: .plain)
            tableView.translatesAutoresizingMaskIntoConstraints = false
    
            tableView.register(CourseCell.self, forCellReuseIdentifier: "CourseCell")
    
            tableView.delegate = self
            tableView.dataSource = self
    
            return tableView
        }()
    
        private var courses: [Course] = []
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            setupViews()
            setupLayout()
    
            fetchCourses()
        }
    
        private func setupViews() {
            view.addSubview(tableView)
        }
    
        private func setupLayout() {
            tableView.snp.makeConstraints { make in
                make.edges.equalToSuperview()
            }
        }
    
        private func fetchCourses() {
            guard let url = URL(string: "https://swiftbook.ru//wp-content/uploads/api/api_courses") else {
                return
            }
    
            URLSession.shared.dataTask(with: url) { (data, _, _) in
                guard let data = data else {
                    return
                }
    
                do {
                    // получаем курсы в переменную
                    self.courses = try JSONDecoder().decode([Course].self, from: data)
                    // и мы должны перезагрузить таблицу
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                } catch let error {
                    print(error)
                }
            }.resume()
        }
    }
    
    extension CoursesViewController: UITableViewDelegate, UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            courses.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            guard let cell = tableView.dequeueReusableCell(withIdentifier: "CourseCell", for: indexPath) as? CourseCell else {
                return UITableViewCell()
            }
    
            cell.configure(with: courses[indexPath.row])
    
            return cell
        }
    
        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            120
        }
    
        func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
            120
        }
    }
    

    Cell

    class CourseCell: UITableViewCell {
        private lazy var nameLabel: UILabel = {
            let label = UILabel()
    
            label.numberOfLines = 0
            label.textColor = .black
            label.font = .systemFont(ofSize: 14, weight: .bold)
    
            return label
        }()
    
        private lazy var courseImage = UIImageView(frame: .zero)
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            setupViews()
            setupLayout()
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        func configure(with course: Course) {
            nameLabel.text = course.name
    
            DispatchQueue.global().async {
                guard let stringUrl = course.imageUrl,
                      let imageURL = URL(string: stringUrl),
                      let imageData = try? Data(contentsOf: imageURL)
                else {
                    return
                }
    
                DispatchQueue.main.async {
                    // Make sure it's the same course
                    self.courseImage.image = UIImage(data: imageData)
                }
            }
        }
    
        private func setupViews() {
            courseImage.contentMode = .scaleAspectFill
            contentView.addSubview(nameLabel)
            contentView.addSubview(courseImage)
        }
    
        private func setupLayout() {
            nameLabel.snp.makeConstraints { make in
                make.top.leading.trailing.equalToSuperview().inset(8)
            }
    
            courseImage.snp.makeConstraints { make in
                make.centerX.equalToSuperview().inset(8)
                make.top.equalTo(nameLabel.snp.bottom).offset(12)
                make.height.width.equalTo(80)
            }
        }
    }
    
    • In my opinion, you should check your UI layout to make sure that the image view can be loaded and displayed properly.

    Some Improvement suggestion

    • Course.swift: Please use lower camel case convention for variables name because it’s the common Swift convention
    • CourseCell.swift: Since the course don’t have ID so after a while you load image from background, this cell might be used by another because of the reuse cell mechanism.
    DispatchQueue.main.async {
        // Make sure it's the same course
        if course.id == self.course.id {
            self.courseImage.image = UIImage(data: imageData)
        }
    }
    
    • Use caching mechanism every time you load image from the server so that next time you don’t need to fetch from the server again (you can set timeout for cache)
    • Instead of handing loading image by yourself, you can use well-known 3rd party libs like SDWebImage or KingFisher.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search