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

关联共享userId的两个JSON,在iOS TableViewController中显示活动头像的问题

你的思路完全正确,而且确实需要定义User结构体!

要在活动列表里显示对应用户的头像,必须通过userId把两个数据源关联起来。直接用JSONSerialization处理用户数据不仅繁琐,还容易出错,定义一个符合Decodable的User结构体能让代码更清晰、更易维护。下面是一步步的解决方案:

1. 定义User结构体

先给用户数据创建一个解码结构体,和你现有的Root、Activity保持一致的风格:

struct User: Decodable {
    let userId: Int
    let displayName: String
    let avatarUrl: String
}

这样你就能用JSONDecoder直接解析用户数组,不用手动处理字典了。

2. 优化网络请求逻辑(避免嵌套回调,确保数据加载完成后刷新UI)

你现在嵌套的网络请求会导致代码混乱,而且reloadData的调用时机不对——活动请求完成后就刷新表格,但用户数据可能还没加载好。建议用DispatchGroup来等待两个请求都完成后再更新UI:

// 先定义实例变量存储数据
var activities: [Activity] = []
var users: [User] = []

let dispatchGroup = DispatchGroup()

// 请求活动数据
dispatchGroup.enter()
URLSession.shared.dataTask(with: activitiesURL) { (data, response, err) in
    defer { dispatchGroup.leave() }
    guard let data = data else { return }
    do {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        let result = try decoder.decode(Root.self, from: data)
        self.activities = result.activities
    } catch {
        print("Error decoding activities: ", error)
    }
}.resume()

// 请求用户数据
dispatchGroup.enter()
URLSession.shared.dataTask(with: userURL) { (data, response, err) in
    defer { dispatchGroup.leave() }
    guard let data = data else { return }
    do {
        let decoder = JSONDecoder()
        self.users = try decoder.decode([User].self, from: data)
    } catch {
        print("Error decoding users: ", error)
    }
}.resume()

// 等待两个请求都完成后,在主线程刷新表格
dispatchGroup.notify(queue: .main) {
    self.tableView.reloadData()
}

3. 关联活动与用户头像(处理图片异步加载)

不要把UIImage直接存在Activity结构体里(UIImage无法被Decodable解析,还会占用额外内存),建议在cellForRowAt里根据userId找到对应用户,然后异步加载头像。先给UIImageView写个扩展简化图片加载:

extension UIImageView {
    func loadAvatar(from urlString: String) {
        // 默认显示占位图
        self.image = UIImage(named: "user-icon-image-placeholder-300-grey.jpg")
        
        guard let url = URL(string: urlString) else { return }
        
        URLSession.shared.dataTask(with: url) { [weak self] data, _, err in
            guard let data = data, err == nil else { return }
            let image = UIImage(data: data)
            // 回到主线程更新UI
            DispatchQueue.main.async {
                self?.image = image
            }
        }.resume()
    }
}

然后修改cellForRowAt方法:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
    let activity = activities[indexPath.row]
    
    // 设置金额
    cell.amountLabel.text = String(format: "$%.2f", activity.amount)
    
    // 设置格式化消息
    if let formattedString = activity.message.htmlAttributedString()?.with(font: UIFont(name: "Helvetica Neue", size: 15)!) {
        cell.descriptionLabel.attributedText = formattedString
    }
    
    // 设置日期(建议把DateFormatter定义为实例变量,避免重复创建)
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .medium
    dateFormatter.timeStyle = .short
    cell.dateLabel.text = dateFormatter.string(from: activity.timestamp)
    
    // 设置头像:根据userId匹配用户
    if let user = users.first(where: { $0.userId == activity.userId }) {
        cell.avatarImageView.loadAvatar(from: user.avatarUrl)
    } else {
        cell.avatarImageView.image = UIImage(named: "user-icon-image-placeholder-300-grey.jpg")
    }
    
    return cell
}

额外小建议

  • 可以引入第三方图片缓存库(比如SDWebImage、Kingfisher),它们会自动处理图片缓存、失败重试等逻辑,比自己写的扩展更稳定。
  • 把DateFormatter定义为TableViewController的实例变量,不要每次在cellForRowAt里创建,避免不必要的性能损耗。

内容的提问来源于stack exchange,提问作者tujilar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:27:29