How to add UIViewController as target of UIButton action created in programmatically created UIView?

17,382

Solution 1

If you are adding the button programmatically to a subclass of UIView, then you can do it one of two ways:

  1. You can make the button a property of the view, and then in the viewController that instantiates the view you can set the target of the button as follows:

    [viewSubclass.buttonName addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
    

    This will set the button's target to a method of buttonTapped: in the viewController.m

  2. You can create a protocol in your subview, which the parent viewController will conform to. In your view, when you add your button set it to call a method in your view. Then call the delegate method from that view so that your viewController can respond to it:

In the top your view subclass .h create the protocol:

@protocol ButtonProtocolName

- (void)buttonWasPressed;

@end

Create a property for the delegate:

@property (nonatomic, assign) id <ButtonProtocolName> delegate;

In the subclass .m set your button selector:

[button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];

In the buttonTapped: method call the delegate method:

- (void)buttonTapped:(id)sender {
    [self.delegate buttonWasPressed];
}

In your viewController.h you'll need to make sure it conforms to the protocol:

@interface someViewController : UIViewController <SomeButtonProtocolName>

In your viewController.m when you init your subview, you'll have to set the delegate:

SomeView *view = ... // Init your view
// Set the delegate
view.delegate = self;

Finally, add the delegate method buttonWasPressed to the viewController.m:

- (void)buttonWasPressed {
    // Put code here for button's intended action.
}

Updated to provide Swift example

// Simple delegate protocol.
protocol SomeViewDelegate: class {
  // Method used to tell the delegate that the button was pressed in the subview.
  // You can add parameters here as you like.
  func buttonWasPressed()
}

class SomeView: UIView {
  // Define the view's delegate.
  weak var delegate: SomeViewDelegate?

  // Assuming you already have a button.
  var button: UIButton!

  // Once your view & button has been initialized, configure the button's target.
  func configureButton() {
    // Set your target
    self.button.addTarget(self, action: #selector(someButtonPressed(_:)), for: .touchUpInside)
  }

  @objc func someButtonPressed(_ sender: UIButton) {
    delegate?.buttonWasPressed()
  }
}

// Conform to the delegate protocol
class SomeViewController: UIViewController, SomeViewDelegate {
  var someView: SomeView!

  func buttonWasPressed() {
    // UIViewController can handle SomeView's button press.
  }
}

Additionally, here is a quick example using a closure instead of a delegate. (This can approach also be implemented in ObjC using blocks.)

// Use typeAlias to define closure
typealias ButtonPressedHandler = () -> Void

class SomeView: UIView {
  // Define the view's delegate.
  var pressedHandler: ButtonPressedHandler?

  // Assuming you already have a button.
  var button: UIButton!

  // Once your view & button has been initialized, configure the button's target.
  func configureButton() {
    // Set your target
    self.button.addTarget(self, action: #selector(someButtonPressed(_:)), for: .touchUpInside)
  }

  @objc func someButtonPressed(_ sender: UIButton) {
    pressedHandler?()
  }
}

class SomeViewController: UIViewController {
  var someView: SomeView!

  // Set the closure in the ViewController
  func configureButtonHandling() {
    someView.pressedHandler = {
      // UIViewController can handle SomeView's button press.
    }
  }
}

Solution 2

You can add target and action to button.

[button addTarget:controller/self action:@selector(onTapButton) forControlEvents:UIControlEventTouchUpInside];

Target is controller so If you want to handle touch event in current controller use self. Other wise you should have a pointer/reference to the controller obj, and use that reference instead of the self. onTapButton is selector which will be called when user tap on the button. onTapButton do not taking any parameter, If you want to use with parameter use onTapButton:

- (IBAction/void)onTapButton{
}
-(IBAction/void)onTapButton:(id)sender{
}

NOTE: Better way is to handle this is to use delegation pattern, have target in self what ever class that is, and After that call delegate, and controller should implement that delegate. Keeping direct reference to the controller is not good practice.

Share:
17,382
Ameet Dhas
Author by

Ameet Dhas

I have completed post graduation in Advanced computer science from university of Leicester. I am here to learn and contribute my knowledge.

Updated on June 23, 2022

Comments

  • Ameet Dhas
    Ameet Dhas almost 2 years

    I created a UIView programmatically and added a UIButton as it's subview.
    I want a UIViewController to be the target of that button action.
    How would I do that?
    If it was created by Interface Builder then it was easy by using IBAction.

  • rdelmar
    rdelmar over 10 years
    This won't work. The OP is adding the button in a subclass of a view, so self will be the view, not the view controller.
  • Adnan Aftab
    Adnan Aftab over 10 years
    In that case he should have reference to the controller and replace self with view controller.
  • Ameet Dhas
    Ameet Dhas over 10 years
    thanks for answering, also how should i set UITextField delegate as UIViewController?
  • Aron C
    Aron C over 10 years
    If you have a UITextField in the view then you can do it the same way as I've illustrated above. However, if you use option 2 you would have to set the view subclass as the textfield delegate, then use the view's delegate to communicate changes to the viewController. In the case of the textField it would be easier to use option 1.
  • Aron C
    Aron C about 10 years
    C_X: I know it has been a few months since you made this response, but I wanted to point out that your second comment actually violates MVC. The view itself should not act on behalf of the viewController, and so using a reference to a viewController within the view subclass is a bad idea. Best practice would be to implement a delegate so that the viewController can act for the view when it calls a certain method. (The accepted answer has an example of this.)
  • leftspin
    leftspin over 8 years
    This is way more complex than it needs to be. See my answer to a similar question here: stackoverflow.com/questions/15361391/…
  • Aron C
    Aron C over 8 years
    @leftspin, IMO, your linked should be considered a bad practice as it is not explicit in what the expected behavior should be. Sure, it cuts down some boiler plate code, but obscures the implementation.
  • leftspin
    leftspin over 8 years
    @AronCrittendon My answer is simply using the Chain-of-responsibility pattern (en.wikipedia.org/wiki/Chain-of-responsibility_pattern) that "promotes the idea of loose coupling, which is considered a programming best practice", and is the core idea behind the responder chain. It no more obscures implementation than using NSNotifications, delegates, or even (in a sense) method overloading; all of which are techniques for indirection to promote loose coupling. For a good discussion see cocoanetics.com/2012/09/the-amazing-responder-chain