10

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.

staticVoidMan
  • 19,275
  • 6
  • 69
  • 98
Bhushan B
  • 2,470
  • 4
  • 26
  • 38

5 Answers5

25

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.
    }
  }
}
Aron C
  • 838
  • 1
  • 10
  • 17
  • thanks for answering, also how should i set UITextField delegate as UIViewController? – Bhushan B Nov 26 '13 at 18:00
  • 1
    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 Nov 26 '13 at 18:08
  • 1
    This is way more complex than it needs to be. See my answer to a similar question here: http://stackoverflow.com/questions/15361391/trigger-a-method-in-uiviewcontroller-from-its-view/32278949#32278949 – leftspin Aug 28 '15 at 20:16
  • @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. – Aron C Aug 28 '15 at 20:24
  • 1
    @AronCrittendon My answer is simply using the Chain-of-responsibility pattern (https://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 http://www.cocoanetics.com/2012/09/the-amazing-responder-chain/ – leftspin Sep 02 '15 at 17:12
1

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.

Adnan Aftab
  • 14,377
  • 4
  • 45
  • 54
  • 1
    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. – rdelmar Nov 26 '13 at 16:38
  • In that case he should have reference to the controller and replace self with view controller. – Adnan Aftab Nov 26 '13 at 16:40
  • 2
    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.) – Aron C Mar 07 '14 at 15:36
0
[buttonName addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];

Now, if you are using UINavigationController your buttonPressed function will be

- (void)buttonPressed:(id)sender
{
    NewViewController *newVC = [NewViewController new];
    [self.navigationController pushViewController:newVC animated:YES];

}

If you are not using navigation Controller than

- (void)buttonPressed:(id)sender
{
    NewViewController *newVC = [NewViewController new];
    [self presentViewController:newVC animated:YES completion:nil];

}
Vladyslav Zavalykhatko
  • 15,202
  • 8
  • 65
  • 100
Kumar
  • 1,882
  • 2
  • 27
  • 44
0

We can achieve this without delegates as well.

In your CustomUIView.m class, implement the following method:-

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];

    NSArray *viewsInNib = [[NSBundle mainBundle]loadNibNamed:@"AddressAlertView" owner:self options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }

    return self;
}

Explanation:- In initWithFrame method, I am loading the current nib. Loading the nib means initialising all the sub view which it contains. Get the fully initilised view of same class and assigned to self. Return self which contains fully initilised uiview.

In your viewcontroller.m file, write the below code to add custom uiview and set target of button:-

UIWindow *mainWindow = [[[UIApplication sharedApplication]delegate]window];

AddressAlertView *objAddressAlertView = [[AddressAlertView alloc] initWithFrame:mainWindow.bounds];
[objAddressAlertView.btnCross addTarget:self
                                 action:@selector(dummy)
                       forControlEvents:UIControlEventTouchUpInside];
if (objAddressAlertView)
    [mainWindow addSubview:objAddressAlertView];
pkc456
  • 8,350
  • 38
  • 53
  • 109
0

For Swift 4

class MyView: UIView {

 weak var delegate: MyBtnDelegate?

 var myBtn: UIButton = {
    let myCustomButton = UIButton()

    // Button UI code goes here

    myCustomButton.addTarget(self, action: #selector(saveData), for: .touchUpInside)
    return myCustomButton
}()


@objc func saveData() {

delegate?.doSomething()

}

In ViewController

protocol MyBtnDelegate: class {
func doSomething()
}

class ViewController: UIViewController, MyBtnDelegate {
var customView = MyView()

 override func viewDidLoad() {
    super.viewDidLoad()
    customView.delegate = self  // Unless you add this line code won't be working
}

func doSomething() {

//Do whatever you want

  }
}

Hope this helps. Happy coding :]

theNoobDev10
  • 409
  • 5
  • 6