如何使UIViewController成为UITableViewCell中UICollectionView的代理以契合MVC架构
Great question! When dealing with nested collection views inside table view cells, keeping your MVC architecture clean means making sure your view controller handles the business logic and interactions, while the cells stay focused solely on displaying content. Here's a step-by-step approach to make your view controller act as the collection view's handler using a custom delegate pattern—this keeps everything aligned with MVC principles:
Step 1: Create a Custom Delegate Protocol for Your Table View Cell
First, define a protocol in your table view cell class. This protocol will act as a bridge to pass collection view events and data requests up to your view controller. It should declare the methods you need (like handling selection, providing data counts, or configuring collection view cells).
import UIKit // Protocol to bridge collection view interactions to the view controller protocol NestedCollectionCellDelegate: AnyObject { // Ask for the number of items in the collection view for a specific cell func numberOfItemsInCollection(for cell: YourTableViewCell) -> Int // Provide data for a specific collection view item func dataForCollectionItem(at indexPath: IndexPath, in cell: YourTableViewCell) -> YourDataModel? // Handle selection of a collection view item func collectionItemSelected(at indexPath: IndexPath, in cell: YourTableViewCell) } class YourTableViewCell: UITableViewCell { // Weak reference to avoid retain cycles (critical for MVC memory management) weak var delegate: NestedCollectionCellDelegate? @IBOutlet weak var collectionView: UICollectionView! override func awakeFromNib() { super.awakeFromNib() // Set the collection view's dataSource and delegate to the cell itself collectionView.dataSource = self collectionView.delegate = self // Register your collection view cell if you haven't done it in IB collectionView.register(UINib(nibName: "YourCollectionCell", bundle: nil), forCellWithReuseIdentifier: "YourCollectionCell") } }
Step 2: Make the Table View Cell Forward Collection View Calls to Its Delegate
Extend your table view cell to conform to UICollectionViewDataSource and UICollectionViewDelegate. In these methods, instead of handling logic directly, you'll call the corresponding methods on your custom delegate (which will be your view controller).
extension YourTableViewCell: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // Delegate the count request to the view controller return delegate?.numberOfItemsInCollection(for: self) ?? 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCollectionCell", for: indexPath) as! YourCollectionCell // Get data from the view controller and configure the collection cell if let data = delegate?.dataForCollectionItem(at: indexPath, in: self) { cell.configure(with: data) // Assume you have a configure method on your collection cell } return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { // Forward the selection event to the view controller delegate?.collectionItemSelected(at: indexPath, in: self) } }
Step 3: Have Your View Controller Conform to the Custom Delegate Protocol
Now, in your view controller, adopt the NestedCollectionCellDelegate protocol. Here, you'll implement the methods to provide data and handle interactions—this is where your MVC controller logic lives.
First, make sure your view controller has a data source (like an array of sections, each containing items for the collection view):
class YourViewController: UIViewController { @IBOutlet weak var tableView: UITableView! // Example data structure: each section has a collection of items var sectionData: [[YourDataModel]] = [] override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self tableView.register(UINib(nibName: "YourTableViewCell", bundle: nil), forCellReuseIdentifier: "YourTableViewCell") // Load your data here loadSectionData() } private func loadSectionData() { // Populate sectionData with your actual data } }
Then, extend the view controller to conform to the custom delegate and implement the required methods:
extension YourViewController: NestedCollectionCellDelegate { func numberOfItemsInCollection(for cell: YourTableViewCell) -> Int { // Find the table view index path for the cell to get the correct section data guard let indexPath = tableView.indexPath(for: cell) else { return 0 } return sectionData[indexPath.row].count } func dataForCollectionItem(at indexPath: IndexPath, in cell: YourTableViewCell) -> YourDataModel? { guard let cellIndexPath = tableView.indexPath(for: cell) else { return nil } return sectionData[cellIndexPath.row][indexPath.item] } func collectionItemSelected(at indexPath: IndexPath, in cell: YourTableViewCell) { // Handle the selection logic here (MVC controller responsibility) guard let cellIndexPath = tableView.indexPath(for: cell) else { return } let selectedItem = sectionData[cellIndexPath.row][indexPath.item] // Example: Push a detail view controller let detailVC = DetailViewController() detailVC.data = selectedItem navigationController?.pushViewController(detailVC, animated: true) } }
Step 4: Connect the Delegate in cellForRowAt
Finally, when dequeuing your table view cell, set the view controller as the cell's delegate:
extension YourViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sectionData.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withReuseIdentifier: "YourTableViewCell", for: indexPath) as! YourTableViewCell // Set the delegate to self (the view controller) cell.delegate = self // Optional: Configure any table cell-specific UI here return cell } }
Why This Works for MVC
- View Layer: Your table view cell and collection view cell are pure views—they don't hold business logic, just forward events and display data provided by the controller.
- Controller Layer: Your view controller handles all data fetching, business logic, and user interactions—exactly what MVC expects from a controller.
- Model Layer: Your
YourDataModelholds the raw data, separate from views and controllers.
If you're using Objective-C, the pattern is identical—just use @protocol with weak (or assign for older iOS versions) properties, and implement the delegate methods in your view controller.
内容的提问来源于stack exchange,提问作者Piyush Vaish

