Sobes.tech
Middle+

How to use the MVC architecture to avoid controller overload?

sobes.tech AI

Answer from AI

MVC (Model-View-Controller) in iOS often leads to "fat" controllers due to placing business logic and presentation logic within them. To address this issue, the following approaches can be used:

  • Delegating presentation logic to View: Creating custom UIView or table/collection view cells that handle data display and basic user actions (e.g., button presses inside the cell). The controller only configures the View with the necessary data.

    class MyCustomView: UIView {
        private let label = UILabel()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            addSubview(label)
            // Setting up constraints, etc.
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        func configure(with text: String) {
            label.text = text
        }
    
        // Handling UIControlEvents, e.g., via addTarget
    }
    
  • Delegating business logic to Model or separate Service Objects: Creating classes responsible for specific operations (e.g., network requests, database interactions, data validation). The controller delegates these tasks to those objects.

    class UserService {
        func fetchUsers(completion: @escaping ([User]?, Error?) -> Void) {
            // Network request logic
        }
    }
    
    class ProfileViewController: UIViewController {
        private let userService = UserService()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            userService.fetchUsers { users, error in
                // Update UI based on received data or error
            }
        }
    }
    
  • Using Data Source and Delegate Protocols: Implementing UITableViewDataSource, UICollectionViewDataSource, UITableViewDelegate, UICollectionViewDelegate protocols in separate classes rather than in the controller itself. The controller only owns instances of these classes.

    class UsersTableViewDataSource: NSObject, UITableViewDataSource {
        private var users: [User] = []
    
        func update(users: [User]) {
            self.users = users
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return users.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
            let user = users[indexPath.row]
            cell.textLabel?.text = user.name
            return cell
        }
    }
    
    class UsersViewController: UIViewController {
        @IBOutlet weak var tableView: UITableView!
        private let dataSource = UsersTableViewDataSource()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.dataSource = dataSource
            // Load data and pass to dataSource.update(users:)
        }
    }
    
  • Applying Coordinator Pattern: Creating separate objects responsible for navigation between screens. Controllers only know that they need to transition to the next step, but not which controller will be next.

    protocol AppCoordinator: AnyObject {
        func start()
        func showUserDetails(user: User)
    }
    
    class MainAppCoordinator: AppCoordinator {
        private let navigationController: UINavigationController
    
        init(navigationController: UINavigationController) {
            self.navigationController = navigationController
        }
    
        func start() {
            let usersVC = UsersViewController()
            // Setting delegate, dataSource, etc.
            navigationController.pushViewController(usersVC, animated: false)
        }
    
        func showUserDetails(user: User) {
            let userDetailsVC = UserDetailsViewController(user: user)
            navigationController.pushViewController(userDetailsVC, animated: true)
        }
    }
    

These approaches help unload the controller, making it more "thin" and focused on managing the View's lifecycle and coordinating interactions between Model and View, rather than executing all application logic.

How to use the MVC architecture to avoid controller… - sobes.tech