如何在UITableView单元格中切换Realm对象并刷新数据?
Hey there! I see you're trying to refresh just the specific cell where the swap button was tapped, instead of reloading the entire table. Let's break this down into simple, actionable steps to get this working right.
First: Get the IndexPath of the Tapped Cell
The key here is figuring out which cell's button was pressed so we can target only that one. In your swapButtonPressed method, we can trace the button's parent views to find its parent cell, then get the corresponding IndexPath from the table view:
@IBAction func swapButtonPressed(_ sender: UIButton) { // Find the parent cell by traversing up the view hierarchy var currentView = sender as UIView var targetCell: WorkoutCell? while let superview = currentView.superview { if let cell = superview as? WorkoutCell { targetCell = cell break } currentView = superview } guard let cell = targetCell, let tableView = cell.superview as? UITableView else { return // Exit if we can't find the cell or table view } let indexPath = tableView.indexPath(for: cell)! swapAndUpdateCell(at: indexPath, in: tableView) }
This traversal method is more reliable than hardcoding superview?.superview because it works even if you adjust your cell's layout later.
Option 1: Directly Update the Cell's UI (Quick Fix)
If you don't need to update a backing data source (though I'd recommend option 2 for consistency), you can generate the new exercise and update the cell's labels directly:
func swapAndUpdateCell(at indexPath: IndexPath, in tableView: UITableView) { let realmExercisePool = realm.objects(ExerciseGeneratorObject.self) guard !realmExercisePool.isEmpty else { print("No exercises available to swap!") return // Prevent crash if the pool is empty } // Generate your new exercise let randomIndex = Int(arc4random_uniform(UInt32(realmExercisePool.count))) let newExercise = realmExercisePool[randomIndex].generateExercise() // Grab the cell and update its labels if let targetCell = tableView.cellForRow(at: indexPath) as? WorkoutCell { targetCell.exerciseName.text = newExercise.name targetCell.repsNumber.text = "\(newExercise.reps)" // Adjust this to match your WorkoutExercise's rep property } }
Option 2: Update Data Source + Refresh Single Cell (Recommended)
This is the better approach because it keeps your data source in sync with the UI, which prevents issues when cells get reused later. We'll use a closure to let the cell notify the view controller when the swap button is tapped:
First, update your WorkoutCell class to add a closure property and a configure method:
class WorkoutCell : UITableViewCell { @IBOutlet weak var exerciseName: UILabel! @IBOutlet weak var repsNumber: UILabel! // Closure to notify the view controller about the tap var onSwapTapped: (() -> Void)? @IBAction func swapButtonPressed(_ sender: Any) { onSwapTapped?() // Trigger the closure when the button is pressed } // Helper method to set up the cell with exercise data func configure(with exercise: WorkoutExercise) { exerciseName.text = exercise.name repsNumber.text = "\(exercise.reps)" } }
Then, in your view controller's tableView(_:cellForRowAt:) method, set up the closure to update the data source and refresh the cell:
// Assume you have a data source array like this in your view controller var workoutExercises: [WorkoutExercise] = [] func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "WorkoutCell", for: indexPath) as! WorkoutCell let currentExercise = workoutExercises[indexPath.row] // Configure the cell with current data cell.configure(with: currentExercise) // Set up the swap closure cell.onSwapTapped = { [weak self] in guard let self = self else { return } let realmExercisePool = self.realm.objects(ExerciseGeneratorObject.self) guard !realmExercisePool.isEmpty else { print("No exercises in pool!") return } // Generate new exercise let randomIndex = Int(arc4random_uniform(UInt32(realmExercisePool.count))) let newExercise = realmExercisePool[randomIndex].generateExercise() // Update the data source self.workoutExercises[indexPath.row] = newExercise // Only reload the tapped cell - no full table reload! tableView.reloadRows(at: [indexPath], with: .automatic) } return cell }
Why Your Previous Attempts Didn't Work
Trying to access cell.workoutName inside the cell itself was off—you could have used self.exerciseName directly, but the bigger issue was not having the IndexPath to ensure you're updating the right data (and avoiding reuse bugs). The closure approach lets the view controller handle data updates, which is the proper separation of concerns in MVC.
Also, don't forget that check for an empty realmExercisePool—it'll save you from a crash if there are no exercises to swap to!
内容的提问来源于stack exchange,提问作者nc14

