关联共享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
相关产品推荐
相关产品推荐

