Structuring UITableView in VIPER

523 Views Asked by At

Imagine I have a viewForHeaderInSection like this:

Note: This is currently written in MVP and I am trying to refactor it to VIPER

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        
    guard let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: DetailsSectionHeaderView.identifier) as? DetailsSectionHeaderView, 
    let section = PresenterEnum.Sections(rawValue: section), 
    section != .fourth, let presenter = presenter else { 
        return nil 
    }
        
    switch section {
        case .first:
            header.configure(StringConstants.first.localized, showButton: false, isSelected: false)
        case .second:
            let secondArray = presenter.secondArrayCount
            header.configure(StringConstants.second.localized, showButton: secondArray > 3, isSelected: presenter.isExpanded(section.rawValue))
            header.showMoreAction = { [weak self] in
                self?.handleMoreAction(tableView, in: section.rawValue)
            }
        case .third:
            let thirdCount = presenter.thirdArrayCount
            header.configure(StringConstants.third.localized, showButton: thirdArrayCount > 3, isSelected: presenter.isExpanded(section.third))
            header.showMoreAction = { [weak self] in
                self?.handleMoreAction(tableView, in: section.rawValue)
            }
        default:
            return nil
        }
        
    return header
}

private func handleMoreAction(_ tableView : UITableView, in section : Int){
    guard let presenter = presenter else {return}
        
    tableView.beginUpdates()
    let isExpanded = presenter.isExpanded(section)
        
    var indexPaths = [IndexPath]()
        
    for i in presenter.minimumRowCount..<presenter.totalRowCountForSection(section){
        indexPaths.append([section, i])
    }
        
    if isExpanded{
       tableView.deleteRows(at: indexPaths, with: .automatic)
    }else{
       tableView.insertRows(at: indexPaths, with: .automatic)
    }
        
    presenter.updateExpansion(section)
    tableView.endUpdates()
}

UITableViewDelegate and UITableViewDataSource are part of the View. But they are passive and shouldn't really request information from the presenter. But at the same time the HOW to display a view information belongs to the presenter.

Going by the definition above I can't figure out how to refactor the code below. It seems incorrect cause the logic about what the section should display is inside the view and how to handle the showMoreAction as well.

So my question is: What is the "correct" way of structuring the above two methods to make it more VIPER compliant?

1

There are 1 best solutions below

4
On

1 Create protocol which allows view and presenter to pass data, example :

protocol PresenterProtocol where Self: UIViewController{
   var table: UITableView {get}
}
  1. Create weak view controller reference in the Presenter, that conforms Presenter protocol, expample:

    class Presenter: NSObject {
    
      weak var viewController: PresenterProtocol{
         didSet{
           connectVC()
         }
      }
    
      func connectVC(){
         self.viewController.table.delegate = self
         self.viewController.table.dataSource = self
      }
    }
    
  2. Now when you assign tableview delegate and data source to presenter you can make an extension, example:

     extension Presenter: UITableViewDataSource , ...
     // cell for row methods .. 
    
  3. Then you should change your view(ViewController)

     class ViewController: UIViewController{
         @IBOutlet weak var tableView: UITableView?
         var presenter: Presenter?
    
    
         viewDidLoad(){
            super.viewDidLoad()
            presenter = Presenter()
            presenter.viewController = self
         }
     }
    

So you assigned your vc to the presenter's viewController Now all you need is to conform your VC to PresenterProtocol

      extension ViewController: PresenterProtocol{
        var table: UITableView {
            return self.tableView
        }
      }

Now your VC is clean, and all logic contains in presenter, sure thing it can be much easier, but I just showing the right direction