Swift UITableViewCell通过API加载图片返回nil显示空白排查
问题描述
Swift开发初学者,通过API拉取数据在UITableView Cell中加载图片,项目可正常构建运行,但单元格图片区域始终显示空白,调试发现对应图片字段返回nil,排查一整天未找到问题原因。
- 使用的API地址:
https://swiftbook.ru//wp-content/uploads/api/api_courses - 运行效果截图:

- 调试nil位置截图:

原有实现代码
CourseCell 单元格实现代码
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) } } } }
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? }
CoursesViewController中网络请求相关代码
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() } }
问题根因
imageUrl字段为nil、图片加载不出来的核心原因是JSON解码时字段映射不匹配:你把Course结构体的所有属性都设为了可选值,JSONDecoder遇到Key不匹配的情况不会抛出解码错误,只会把对应属性赋值为nil,所以看不到报错,排查起来很隐蔽。
另外当前用Data(contentsOf:)加载网络图片的写法存在缺陷,会导致图片加载不稳定、cell复用错位等问题。
排查&修复步骤
1. 先确认接口返回的真实字段名
在fetchCourses方法的do块里,解码Course之前,加一段代码打印接口返回的真实字段,避免拼写/命名规则不一致的问题:
do { // 先打印接口返回的原始结构,确认图片字段名 if let jsonList = try JSONSerialization.jsonObject(with: data) as? [[String: Any]], let firstCourse = jsonList.first { print("接口返回的课程字段列表:\(firstCourse.keys)") } self.courses = try JSONDecoder().decode([Course].self, from: data) DispatchQueue.main.async { self.tableView.reloadData() } } catch let error { print("解码错误:\(error)") }
如果打印出来的图片字段名不是imageUrl(比如是image_url、link等),直接对应修改Course模型的属性名即可。
2. 统一字段映射规则,避免隐式匹配问题
该接口返回的课程字段采用蛇形命名法(比如number_of_lessons、number_of_tests),如果没有显式指定映射规则,很容易因为命名风格不一致导致字段匹配失败,推荐用系统提供的蛇形转驼峰策略自动处理映射:
修改Course模型,统一用驼峰命名
struct Course: Decodable { let name: String? let imageUrl: String? let numberOfLessons: Int? let numberOfTests: Int? }
修改解码逻辑,开启蛇形转驼峰
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 { let decoder = JSONDecoder() // 自动将接口返回的蛇形字段转为驼峰属性名 decoder.keyDecodingStrategy = .convertFromSnakeCase self.courses = try decoder.decode([Course].self, from: data) DispatchQueue.main.async { self.tableView.reloadData() } } catch let error { print("解码错误:\(error)") } }.resume() }
3. 替换图片加载逻辑,禁止用Data(contentsOf:)加载网络图片
Data(contentsOf:)是为读取本地文件设计的同步方法,用来加载网络资源没有超时处理、没有错误回调,还会因为UITableView的cell复用机制导致图片错位,替换为URLSession的异步网络请求写法:
func configure(with course: Course) { // 复用时先清空上一张图片,避免错位显示 courseImage.image = nil courseNameLabel.text = course.name numberOfLessons.text = "Number of lessons \(course.numberOfLessons ?? 0)" numberOfTests.text = "Number of tests \(course.numberOfTests ?? 0)" guard let stringUrl = course.imageUrl, let imageURL = URL(string: stringUrl) else { return } // 异步加载网络图片 URLSession.shared.dataTask(with: imageURL) { [weak self] data, _, error in guard let data = data, error == nil, let image = UIImage(data: data) else { return } DispatchQueue.main.async { self?.courseImage.image = image } }.resume() }
额外注意
- 如果打印出来的图片链接是http开头的,需要在项目的
Info.plist里添加App Transport Security Settings配置,允许对应域名的http请求,否则非HTTPS资源会被系统拦截加载失败。 - 熟悉基础逻辑之后,图片加载可以直接用成熟的第三方库,自动处理缓存、复用、解码问题,不用重复写基础逻辑。
内容的提问来源于stack exchange,提问作者V777
相关产品推荐
相关产品推荐

