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

Swift UITableView分组加载用户数据时触发索引越界致命错误求助

Fixing the "Index out of range" Error in Your Firebase User Directory

Let's break down why you're hitting that crash and walk through the fixes to get your department-grouped user list working properly.

Why You're Getting the Crash

Your current code has two critical issues that lead to the index out of range error:

  1. Re-grouping data every time a cell loads
    You're creating departmentSections inside cellForRowAt, which runs every time a cell appears (including when scrolling). This means the grouped data can change unexpectedly (e.g., if Firebase is still loading users into directoryArray), leading to a mismatch between the number of sections/rows the table expects and the actual grouped data.

  2. Missing synchronized table view data source methods
    Even if you have numberOfSections(in:) or tableView(_:numberOfRowsInSection:) implemented, they’re probably not tied to the dynamically grouped data. If the table thinks there are more sections/rows than your grouped array has, accessing rowsInSection[indexPath.section] will crash immediately.

Step-by-Step Fix

1. Move Grouped Data to Class Properties

First, store your grouped users and department names as properties in your view controller. This way, you only compute the grouping once when your Firebase data is ready, not every time a cell loads.

// Add these properties to your view controller
var groupedUsers: [[User]] = [] // Each element is an array of users in one department
var departmentTitles: [String] = [] // Stores department names for section headers

2. Group Your Data Once (When Firebase Loads)

When you fetch the user list from Firebase, process the grouping there, then refresh the table view. Replace your Firebase data fetch completion code with something like this:

// Example Firebase fetch completion (adjust to match your actual code)
Firestore.firestore().collection("users").getDocuments { snapshot, error in
    guard let documents = snapshot?.documents else {
        print("Error fetching users: \(error!)")
        return
    }
    
    // Convert documents to your User model
    self.directoryArray = documents.compactMap { doc in
        try? doc.data(as: User.self)
    }
    
    // Group users by department and sort departments for consistency
    let departmentDict = Dictionary(grouping: self.directoryArray) { $0.department }
    self.departmentTitles = departmentDict.keys.sorted() // Sort departments alphabetically
    self.groupedUsers = self.departmentTitles.map { departmentDict[$0]! }
    
    // Refresh the table view now that data is ready
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

3. Update Table View Data Source Methods

Make sure these methods are implemented to match your grouped data:

override func numberOfSections(in tableView: UITableView) -> Int {
    return groupedUsers.count
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return groupedUsers[section].count
}

// Optional: Add section headers to show department names
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return departmentTitles[section]
}

4. Clean Up cellForRowAt

Now you can simplify this method to use the pre-grouped data, and fix other potential issues like forced unwrapping:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "directoryCell", for: indexPath) as! TableViewCell
    
    // Get the user from our pre-grouped array (no more dynamic grouping here!)
    let user = groupedUsers[indexPath.section][indexPath.row]
    
    // Load profile pic safely (avoid forced unwrapping!)
    if let profilePicURLString = user.profilePic, let url = URL(string: profilePicURLString) {
        URLSession.shared.dataTask(with: url) { [weak cell] data, _, error in
            guard let data = data, error == nil else {
                print("Error loading profile pic: \(error ?? NSError(domain: "", code: -1, userInfo: nil))")
                return
            }
            DispatchQueue.main.async {
                cell?.profilePicView.image = UIImage(data: data)
                // No need to call layoutSubviews() manually—UIKit handles this
            }
        }.resume()
    } else {
        // Set a default profile pic if the URL is missing/invalid
        cell.profilePicView.image = UIImage(named: "default-profile")
    }
    
    // Populate cell labels
    cell.displayNameLabel.text = user.displayName
    cell.emailLabel.text = user.email
    cell.officeLabel.text = user.office
    cell.titleLabel.text = user.title
    cell.profilePicView.layer.masksToBounds = true
    
    return cell
}

Bonus Tips to Avoid Future Issues

  • Use an image caching library: Instead of raw URLSession for profile pics, use libraries like Kingfisher or SDWebImage. They handle caching, placeholder images, and canceling pending requests automatically when cells are reused.
  • Never force-unwrap optional values: Always use optional binding (if let) to handle cases where data might be missing (like a user without a profile pic URL).
  • Ensure Firebase data is fully loaded before reloading the table: Always call tableView.reloadData() inside the main thread after your directoryArray is fully populated.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:14:21