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

Swift中Facebook Taggable Friends无法加载至UITableView问题求助

Let's break down what's going wrong with both approaches and fix them step by step.

What's Wrong with Your First Approach

Putting the FBSDKGraphRequest inside cellForRowAt is a big no-no. Here's why:

  • cellForRowAt gets called every time a cell comes into view (when scrolling, reloading, etc.). This means you're firing hundreds of duplicate network requests, which wastes bandwidth and causes your table to refresh constantly (the 2-3 second refreshes you're seeing).
  • When the request completes asynchronously, the cell might have already been reused for a different row. This leads to wrong names/avatars appearing in the wrong cells (the "order错乱" issue).

What's Wrong with Your Second Approach

Your second attempt is closer, but it has two critical issues causing crashes:

  1. Force-unwrapping optional values: try? Data(contentsOf: loadurl!) and UIImage(data: dataurl!)! will crash if the URL is invalid, the network request fails, or the data isn't a valid image. Never force-unwrap (!) unless you're 100% sure the value isn't nil.
  2. No table view reload after data loads: You're populating friendnames and friendpictures asynchronously, but you never tell the table view to refresh. So when the table first loads, the arrays are empty, and accessing friendnames[indexPath.row] causes an index out-of-bounds crash.
  3. Synchronous image loading: Data(contentsOf:) runs on the main thread, which blocks your UI and makes the app freeze while loading images.

The Fixed Solution

Let's rewrite this properly with best practices:

Step 1: Create a Friend Model

First, use a struct to organize your friend data (this avoids managing separate arrays and keeps data in sync):

struct Friend {
    let id: String
    let name: String
    let avatarURL: URL?
}

Step 2: Declare a Single Data Array

Replace your two separate arrays with one array of Friend objects:

var friends: [Friend] = []

Step 3: Fetch Friends Once (and Safely)

Update your loadtaggableFriends method to fetch data once, parse it safely, and reload the table view when done:

func loadtaggableFriends() {
    FBSDKGraphRequest(graphPath: "me/taggable_friends?limit=5000", parameters: nil).start { [weak self] (connection, result, error) in
        guard let self = self else { return }
        
        // Handle errors
        if let error = error {
            print("Failed to fetch friends: \(error.localizedDescription)")
            return
        }
        
        // Parse the response safely (no force-unwrapping!)
        guard let responseDict = result as? [String: Any],
              let friendsData = responseDict["data"] as? [[String: Any]] else {
            print("Invalid response format")
            return
        }
        
        // Build our friends array
        var fetchedFriends = [Friend]()
        for friendDict in friendsData {
            guard let id = friendDict["id"] as? String,
                  let name = friendDict["name"] as? String else {
                continue // Skip invalid entries instead of crashing
            }
            
            // Parse avatar URL safely
            let avatarURL: URL?
            if let pictureDict = friendDict["picture"] as? [String: Any],
               let pictureData = pictureDict["data"] as? [String: Any],
               let urlString = pictureData["url"] as? String {
                avatarURL = URL(string: urlString)
            } else {
                avatarURL = nil
            }
            
            fetchedFriends.append(Friend(id: id, name: name, avatarURL: avatarURL))
        }
        
        // Update data on the main thread and reload the table
        DispatchQueue.main.async {
            self.friends = fetchedFriends
            self.tableView.reloadData()
        }
    }
}

Step 4: Configure Cells Properly

Update cellForRowAt to use the friends array, load avatars asynchronously, and avoid cell reuse issues:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "friendCell", for: indexPath) as! friendstvc
    let currentFriend = friends[indexPath.row]
    
    // Set the friend's name
    cell.friendName.text = currentFriend.name
    
    // Set a placeholder avatar (so cells don't show old images during reuse)
    cell.profpic.image = UIImage(systemName: "person.circle")
    
    // Load avatar asynchronously
    if let avatarURL = currentFriend.avatarURL {
        URLSession.shared.dataTask(with: avatarURL) { [weak cell] data, _, error in
            guard let data = data, error == nil, let avatarImage = UIImage(data: data) else {
                return
            }
            
            // Update the image on the main thread
            DispatchQueue.main.async {
                // Make sure the cell is still displaying the same friend (prevents reuse bugs)
                if let cellIndexPath = tableView.indexPath(for: cell!), cellIndexPath == indexPath {
                    cell?.profpic.image = avatarImage
                }
            }
        }.resume()
    }
    
    return cell
}

Key Improvements

  • Single network request: We only fetch the friend list once in viewDidLoad, not every time a cell appears.
  • Safe parsing: No force-unwrapping (!)—we use optional binding to handle nil values gracefully, avoiding crashes.
  • Async image loading: Avatars load in the background, so your UI stays responsive. We also check if the cell is still assigned to the same row before updating the image, fixing reuse issues.
  • Data organization: Using a Friend struct keeps all related data together, making your code cleaner and easier to maintain.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:06:09