You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift UITableViewCell通过API加载图片返回nil显示空白排查

问题描述

Swift开发初学者,通过API拉取数据在UITableView Cell中加载图片,项目可正常构建运行,但单元格图片区域始终显示空白,调试发现对应图片字段返回nil,排查一整天未找到问题原因。

  • 使用的API地址:https://swiftbook.ru//wp-content/uploads/api/api_courses
  • 运行效果截图:
    运行效果截图
  • 调试nil位置截图:
    调试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 13:15:35