How can I call presentViewController in UIView class?

3 min read 07-10-2024
How can I call presentViewController in UIView class?


Presenting View Controllers from UIView: Bridging the Gap

Many iOS developers have encountered the frustrating situation where they need to present a new view controller from a simple UIView. The presentViewController method, a core functionality of UIViewController, seems out of reach when working within a UIView's scope. This article will break down the issue, explore potential solutions, and provide practical guidance on how to achieve this functionality effectively.

Understanding the Problem

The issue stems from the fundamental design of iOS's view controller hierarchy. presentViewController is a method exclusive to UIViewController objects, responsible for managing the presentation of view controllers on the screen. UIViews, on the other hand, are responsible for drawing and displaying content within their assigned area. This creates a clear separation of concerns: view controllers manage navigation and presentation, while views focus on visual representation.

Illustrating the Challenge

Let's imagine a scenario where you have a custom button within a UIView, and you want to trigger the presentation of a new view controller when this button is tapped.

class MyCustomView: UIView {

    let myButton = UIButton()

    override init(frame: CGRect) {
        super.init(frame: frame)
        // Add the button to the view
        addSubview(myButton)

        myButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }

    @objc func buttonTapped() {
        // We need to present a new view controller here
        // But `presentViewController` is not accessible in this context!
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

The buttonTapped method cannot directly utilize presentViewController because it resides within a UIView.

Bridging the Gap: Solutions

Fortunately, there are several effective ways to present a view controller from within a UIView:

  1. Delegation: The most common and flexible approach is to delegate the presentation responsibility to the containing view controller. You can define a protocol within your UIView class and have the view controller implement it. The view can then call a method on the view controller to initiate the presentation.

    // In MyCustomView.swift
    protocol MyCustomViewDelegate: AnyObject {
        func didTapButton()
    }
    
    class MyCustomView: UIView {
    
        weak var delegate: MyCustomViewDelegate?
    
        // ... (rest of the code) 
    
        @objc func buttonTapped() {
            delegate?.didTapButton()
        }
    }
    
    // In your View Controller
    class MyViewController: UIViewController, MyCustomViewDelegate {
    
        let myCustomView = MyCustomView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(myCustomView)
            myCustomView.delegate = self 
        }
    
        func didTapButton() {
            let newViewController = NewViewController()
            present(newViewController, animated: true)
        }
    }
    
  2. Closure/Callback: You can pass a closure to your UIView that will be executed when the button is tapped. This closure will be provided by the view controller, allowing it to control the presentation process.

    // In MyCustomView.swift
    class MyCustomView: UIView {
    
        var onButtonTapped: (() -> Void)?
    
        // ... (rest of the code) 
    
        @objc func buttonTapped() {
            onButtonTapped?()
        }
    }
    
    // In your View Controller
    class MyViewController: UIViewController {
    
        let myCustomView = MyCustomView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(myCustomView)
            myCustomView.onButtonTapped = {
                let newViewController = NewViewController()
                self.present(newViewController, animated: true)
            }
        }
    }
    
  3. Target-Action: A simpler approach utilizes the built-in target-action mechanism. You can set the button's target to the containing view controller and define an action method within the view controller that handles the presentation logic.

    // In MyCustomView.swift
    class MyCustomView: UIView {
    
        // ... (rest of the code) 
    
        @objc func buttonTapped() {
            // Do nothing here - action will be handled by the view controller
        }
    }
    
    // In your View Controller
    class MyViewController: UIViewController {
    
        let myCustomView = MyCustomView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(myCustomView)
            myCustomView.myButton.addTarget(self, action: #selector(presentNewViewController), for: .touchUpInside)
        }
    
        @objc func presentNewViewController() {
            let newViewController = NewViewController()
            present(newViewController, animated: true)
        }
    }
    

Choosing the Right Approach

The best approach depends on your specific project and preferences:

  • Delegation: Offers the most flexibility and clear separation of concerns. It's particularly suitable for complex interactions.
  • Closure/Callback: Provides a concise and efficient way to handle actions.
  • Target-Action: A simple and straightforward option for basic interactions.

Remember, understanding the core principles of view controller management and view hierarchy in iOS is crucial for building robust and well-structured applications.