I add a UIButton programmatically, and I want to call a method when pressing on it, I'm not sure how to design such a pattern:
where should I put event handler, or action method? in view controller or view itself.
If I put the method in view controller, how can I manipulate the SubViews of the View in the method? should I expose all of SubViews (UIButton, etc.) to controller by putting them in view's header file as properties? Actually this question shall be asked in this way: How can I implement by code that SubViews in a view are associated to an IBOutlet properties by Interface Builder...
Check this link for the basics of iOS view hierarchy:
Getting started with iOS Views and understand the following diagram (credits: techrepublic.com):
Programmatically:
// Create your button wherever you wish (below is example button)
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeCustom];
myButton.frame = CGRectMake(0.0, 0.0, 320, 450);
[myButton setTitle:#"Yay" forState:UIControlStateNormal];
[myButton addTarget:self action:#selector(didTouchUp:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:myButton];
// This method will be called when touch up is made on the button
- (void)didTouchUp:(UIButton *)sender {
NSLog(#"Button Pressed!");
}
Explanation:
[myButton addTarget:self action:#selector(didTouchUp:) forControlEvents:UIControlEventTouchUpInside];
myButton - your button view
addTarget - the target in which the method exist in
action - the selector you want to call
forControlEvents - control event the user will do to trigger this action
Using Storyboards:
(A) Make sure the storyboard is corresponding to the correct class
(B) Drag UIButton from the object library into the storyboard
Then add to the ViewController.h file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *myButton;
- (IBAction)didTouchUp:(id)sender;
#end
Then add to the ViewController.m file:
- (IBAction)didTouchUp:(id)sender {
NSLog(#"Button Pressed!");
}
Lastly, create the connection between the UIButton and the IBAction:
(A) Right click on the button view
(B) Drag touch up action to the button view on the storyboard
(C) Press on didTouchUp:
This is the very basic flow to do such a step... you might want to expend your skills by reading the following:
Storyboard vs IB vs Code
Stanford University Developing iOS
Related
i think there are two ways to add a UIControl in my view
status 1:
#property (nonatomic,weak) UIButton *button;
- (void)viewDidload
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button........ (set frame,color,text........)
[self.view addSubView:button];
_button = button;
}
status 2.
#property (nonatomic,strong) UIButton *button;
- (void)viewDidload
{
_button = [UIButton buttonWithType:UIButtonTypeCustom];
_button.......(set frame,color,text......)
[self.view addSubView:_button];
}
i want to know the difference between them and in different situation what should i chose ?
in version 1 (nonatomic, strong) the viewcontroller keeps a strong reference to the button. that means if you for example remove it from the superview somewhere in your code ([self.button removeFromSuperview];) it is still in memory and could be readded at a later point in time ([self.view addSubview:self.button];).
in version 2 (nonatomic, weak) the viewcontroller keeps a weak reference to the button. that means if you for example remove it from the superview somewhere in your code (and no other part of your app keeps a strong reference to it) it gets deallocated.
there is no real difference between creating a local variable UIButton *button = ... and assigning it to the instance variable afterwards _button = button; or working with the instance variable directly _button = [UIButton buttonWithType:....
in status 1:
you have marked you UIButton as weak Property
and A weak reference means the pointer has no owner, therefore it will be deallocated as soon as it is no longer needed (that is, nothing else is pointing to it).
but in status 2 :
you have marked you UIButton as Strong Property and when your Button hasn't mark as IBOutlet , you should use strong
I'm experimenting a custom class button that display the UIView when touch start.
I have a UIButton class name it TapInButton. here're codes.
#import "TapInButton.h"
#import "TapInViewController.h"
#interface TapInButton()
#property (nonatomic,strong) TapInViewController * tapIn;
#end
#implementation TapInButton
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.tapIn = [[TapInViewController alloc] initWithNibName:#"TapIn" bundle:nil];
self.tapIn.view.frame = CGRectMake(0, 50, 300, 500);
self.tapIn.view.backgroundColor = [UIColor redColor];
[self.superview addSubview:self.tapIn.view];
}
As you can see, I import TapInViewController that generates xib file.
When the user click the tapin button that has a custom class TapInButton.
It Display the xib file. see image below
I successfully add the xib to the main ViewController but the problem is I cannot click the button inside the xib file. Is there something missing in my code? or Is this possible to generate a uiview inside a custom UIButton?
I know the other way to generate a xib file but not using custom button. I just want to clarify my mind if this is possible. Hoping for your advice or explanation. Thanks in advance
the problem is I connot click the button inside the xib file. Is there something missing in my code?
You'll have to obtain a reference to the button in code and set up the button-click action connection in code (by calling addTarget:action:forControlEvents:).
In view.h I declare
#property (retain, strong) UIButton *btn;
In view.m I first have:
#synthesize btn;
and then in implementation I have this call to a method in another class:
[self.view addSubview:[otherclass getTestBtn:btn]];
After this line, in my view.m, I try to log the text in the button like this:
NSLog(#"btn.titleLabel.text = %#", btn.titleLabel.text);
Unfortunately the logs says:
btn.titleLabel.text = (null)
In the other class.m I implemented the method this way:
btn = [[UIButton alloc] initWithFrame:CGRectMake(70, 340, 100, 25)];
[btn setTitle:#"HELLO" forState:UIControlStateNormal];
return btn;
In the simulator I see that the button shows the text "Hello". But from view.m the text in the button don't seem to be accessible. Why?
Im afraid that the button that is passed from view.m to other class.m is not passed by reference. And hence the button in view.m is not affected by the method in other class. Is that the reason? If yes: how to pass the button as reference? I tried with & character. But the editor shows errors.
If you want to get the button returned from the other class then you need to assign it to your property -
self.btn = [otherClass getTestBtn];
[self.view addSubView:self.btn];
Also, you should strong rather than retain as #dimimpou said and you don't need #synthesize any more unless you want to use a specific backing variable name.
I have a UIViewController with its UIView which contains a UIButton. I want to trigger a method in UIViewController on button click event.
Keeping reference of UIViewController doesn't seem to be a good idea like the following link says:
Get to UIViewController from UIView?
So I want to achive this using a delegate. Any hint on how to achieve this?
You can do something like this
CustomView.h
#import <UIKit/UIKit.h>
#protocol CustomViewDelegate <NSObject>
-(void)didButtonPressed;
#end
#interface CustomView : UIView
#property (assign) id<CustomViewDelegate> delegate;
#end
CustomView.m
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
//[self addSubview:titleLbl];
UIButton *button= [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(100, 100, 100, 50);
[button addTarget:self.delegate action:#selector(didButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"pressMe" forState:UIControlStateNormal];
[self addSubview:button];
}
return self;
}
in your ViewController.m
-(void)loadView
{
[super loadView];
CustomView *view = [[CustomView alloc]initWithFrame:self.view.bounds];
view.delegate = self;
[self.view addSubview:view];
}
This is what the responder chain was built for. When you add a target to your button, just supply nil for the target:
[mySpecialButton addTarget:nil
action:#selector(mySpecialButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
The nil target basically means "send mySpecialButtonTapped: to any object in the responder chain that can handle it".
Now you can handle this selector anywhere in the responder chain, which includes the button itself, its containing view, its containing view controller, the UIApplication, and finally your AppDelegate. Just place this method in the object most appropriate for your needs:
- (void)mySpecialButtonTapped:(id)sender {
NSLog("My special button was tapped!");
}
You don't need delegates or callback blocks (as in the accepted answer) if you just want to bubble a message up.
I guess that you expected something more fundamental then just pass some button action to controller.
I always follow MVC pattern in case of model/view/controller collaboration. It resolve your issue and many other. And I want to share my experience.
Separate controller from view and model: don't put all of the "business logic" into view-related classes; this makes the code very unusable. Make controller classes to host this code, but ensure that the controller classes don't make too many assumptions about the presentation.
Define callback APIs with #protocol, using #optional if not all the methods are required.
For view define protocol like <view class name>Protocol (example NewsViewProtocol). For controller define delegate like <view class name>Delegate (example NewsViewDelegate) and dataSource like <view class name>DataSource (example NewsViewDataSource). Keep all this #protocols in one separate file named <view class name>Protocol.h (example NewsViewProtocol.h)
Short example:
Contents of NewsView.h
//
// NewsView.h
#interface NewsView : UIView <NewsViewProtocol> {
#protected
NSObject* delegate_;
NSObject* dataSource_;
}
#end
Contents of NewsController.h and .m
//
// NewsController.h
#interface NewsController : UIViewController <NewsViewDataSource, NewsViewDelegate> {
}
#property (nonatomic, weak) UIView<NewsViewProtocol>* customView;
#end
#implementation NewsController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView = (UIView<NewsViewProtocol>*)self.view;
[self.customView setDelegate:self];
[self.customView setDataSource:self];
}
#end
Contents of NewsViewProtocol.h
//
// NewsViewProtocol.h
#protocol NewsViewProtocol;
#protocol NewsViewDelegate<NSObject>
#optional
- (void)someAction;
- (void)newsView:(UIView<NewsViewProtocol>*)newsView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
#end
#protocol NewsViewDataSource<NSObject>
#required
- (id)newsView:(UIView<NewsViewProtocol>*)newsView itemAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfItemsInNewsView:(UIView<NewsViewProtocol>*)newsView section:(NSInteger)section;
- (BOOL)newsView:(UIView<NewsViewProtocol>*)newsView shouldDisplaySection:(NSInteger)section;
#end
#protocol NewsViewProtocol<NSObject>
#required
//Never retain delegate instance into implementation of this method
- (void)setDelegate:(NSObject<NewsViewDelegate>*)delegate;
//Never retain delegate instance into implementation of this method
- (void)setDataSource:(NSObject<NewsViewDataSource>*)dataSource;
- (void)reload;
#end
You may consider that it is redundant. In simple view controller, YES. But if you develop very complex screen with huge amount of data then it gives you some advantages as:
Helps you to separate responsibility between view and controller.
Keeps your code clear.
Makes you code more reusable.
Life is easy in xCode.
At the very beginning be sure that your xib View (the one with your button inside it) is associated to the right ViewController class. Which can be the default ViewController class that comes with a new project or your custom one.
After this, here comes the magic trick! Separate your view into 2 panel. The goal is to see your xib and your viewController code (the .m file). Now press the control key of your keyboard and drag your UIButton to the code. Select IBAction. It will generate something you can call a "listener" in other language. Go to the core code of your View Controller and complete the method!
Easy as that! Have fun :)
You don't really need delegates for this - it is how UIButtons are intended to be used. Just control-click and drag from your button to the .m file for your UIViewController. This will create a new method. From there, you can either make a call to the method you wrote or just copy-paste what you have into the new method.
You can try this:
[yourButton addTarget:self action:#selector(yourButtonAction:) forControlEvents:UIControlEventTouchUpInside];
And in your selector specify the action
- (IBAction)yourButtonAction:(id)sender {
//Action to perform
}
To add a button programmatically, in myViewController.m
UIView *yourView = [[UIView alloc] init];
UIButton *yourButton = [[UIButton alloc] initWithFrame:CGRectMake(0,0,100,21)];
[yourButton addTarget:self action:#selector(yourMethod) forControlEvents:UIControlEventTouchDown];
[yourView addSubview:yourButton];
More info here.
How do I add an action to a custom view? Similar to how UIButtons have an action I can connect in Inteface Builder. I can't use a SEL like below because it's not an object, what do I use?
#property(nonatomic, weak) IBOutlet SEL action;
You want to make your custom object a subclass of UIControl (which is a subclass of UIView). Then you get all the target/action methods... if you are doing this in code the one to explore will be
– addTarget:action:forControlEvents:
If you make a custom UIControl object, then you can drag out a custom view in the storyboard and set it's custom class to your customControl. Then you can drag out IBAction links to your viewController just as if it were a button.
Here is how to make one in code...
- (void)viewDidLoad
{
[super viewDidLoad];
UIControl* customControl = [[UIControl alloc]initWithFrame:CGRectMake(20, 20, 200, 200)];
customControl.backgroundColor = [UIColor redColor];
[customControl addTarget:self
action:#selector(customAction:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:customControl];
}
- (void)customAction:(id)sender
{
NSLog (#"touched");
}