Swift UITableView分组加载用户数据时触发索引越界致命错误求助
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:
Re-grouping data every time a cell loads
You're creatingdepartmentSectionsinsidecellForRowAt, 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 intodirectoryArray), leading to a mismatch between the number of sections/rows the table expects and the actual grouped data.Missing synchronized table view data source methods
Even if you havenumberOfSections(in:)ortableView(_: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, accessingrowsInSection[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
URLSessionfor 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 yourdirectoryArrayis fully populated.
内容的提问来源于stack exchange,提问作者Josh

