iosswiftuiviewuiviewcontrolleruiview-hierarchy

Dynamic Creation UIButton AddTarget on UIView executed on UIViewController


Currently this code that executes a Tag Styled List, The issue remains when I want to try to pass the addTarget Action optionClicked to my UIViewController

DrawerView.swift


let menuOptions = ["Info", "Actions", "Users", "Patiens"]

        menuOptions.forEach({

            let button = UIButton()
            button.setTitle($0, for: .normal)

            if $0.contains(menuOptions[0]) {
                button.style(with: .filled)
            } else {
                button.style(with: .outlined)
                button.addTarget(self, action: #selector(optionClicked(_:)), for: .touchUpInside)
            }
            actionStackView.addArrangedSubview(button)
        })

DrawerController.swift


class DrawerController: UIViewController {

    var shareView = DrawerView()
    var viewModel: CarDetailViewModel?

    override func loadView() {
        shareView.viewModel = viewModel
        view = shareView
    }

    @objc func optionClicked(_ sender: UIButton) {

        let feedbackGenerator = UISelectionFeedbackGenerator()
        feedbackGenerator.selectionChanged()

        let optionClicked: String = sender.titleLabel?.text ?? "0"

        switch optionClicked {
        case "Actions": present(DrawerActionController(), animated: true, completion: nil)
        case "Notifications":
            let viewController = DrawerNotificationController()
            viewController.carDetailViewModel = viewModel
           present(viewController, animated: true, completion: nil)

        case "Patients":
            let viewUserController = DrawerUserController()
            viewUserController.carPath = "9000"
           present(viewUserController, animated: true, completion: nil)
        default: break
        }
    }
}

Tried button.addTarget(self, action: #selector(optionClicked(_:)), for: .touchUpInside) but did not work out.


Solution

  • In the method button.addTarget(self, action: #selector(optionClicked(_:)), for: .touchUpInside) you need to provide pointer to the viewController which will receive the action.

    The easiest way in your case would be to create lazy variable DrawerView with DrawerController on the init and use the drawer controller in the button action.

    DrawerView.swift

    class DrawerView: UIView {
    
        private unowned let drawerController: DrawerController
    
        init(drawerController: DrawerController) {
            self.drawerController = drawerController
        }
    
        ... wherever is your code placed ...
    
        let menuOptions = ["Info", "Actions", "Users", "Patiens"]
    
        menuOptions.forEach({
    
            let button = UIButton()
            button.setTitle($0, for: .normal)
    
            if $0.contains(menuOptions[0]) {
                button.style(with: .filled)
            } else {
                button.style(with: .outlined)
                button.addTarget(drawerController, action: #selector(DrawerController.optionClicked(_:)), for: .touchUpInside)
                actionStackView.addArrangedSubview(button)
            }
        })
    
        ....
    }
    

    It's important you use unowned or weak to prevent retain cycles and memory leaks

    To create the DrawerView you can use a lazy variable:

    lazy var shareView: DrawerView = DrawerView(drawerController: self)
    

    lazy will allow you to use self, you can also use optional and create the variable later, but that's basically what lazy does.

    Hope it helps!