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!!!
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)
CodePudding user response:
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 conventionCourseCell.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
orKingFisher
.
CodePudding user response:
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