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");
}
Related
I am building an app where I want to completely avoid using Storyboard and Interface Builder in general, so all UI should be specified in code. I am using PureLayout, a nice API for configuring AutoLayout constraints.
However, my issue is that it seems like AutoLayout is disabled when not using Interface Builder. updateViewConstraints, the method where I put the layout according to the recommendation given by the PureLayout author, is not called at all.
Just to give a bit more info about my setup:
deleted Main.storyboard and removed the entry from my Info.plist
manually setup self.window in AppDelegate.m and added UINavigationController with MainMainController as rootViewController
As mentioned, my main issue is that updateViewConstraints does not get called on MainViewController but the UI elements are all displayed with the frames that I passed to them during initilization.
Note: It seems to me like I just need to enable some flag somewhere to mimic the checkbox from Interface Builder with which you can indicate whether you want to use AutoLayout.
MainViewController.m
#interface MainViewController ()
#property (nonatomic, strong) UIButton *startButton;
#property (nonatomic, assign) BOOL didSetupConstraints;
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:self.startButton];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
}
- (UIButton *)startButton
{
if (!_startButton) {
UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
CGRect startButtonFrame = CGRectMake(75.0, 75.0, 250.0, 44.0);
startButton.frame = startButtonFrame;
[startButton setTitle:#"Start" forState:UIControlStateNormal];
[startButton setTranslatesAutoresizingMaskIntoConstraints:NO];
_startButton = startButton;
}
return _startButton;
}
- (void)updateViewConstraints
{
NSLog(#"Update view contraints");
if (!self.didSetupConstraints) {
[self.startButton autoCenterInSuperview];
self.didSetupConstraints = YES;
}
[super updateViewConstraints];
}
#end
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
I am using Parse anypic tutorial and I want to create a somehow different UI. But I have some troubles.
So, my ViewController is this :
#interface PAPHomeViewController ()
#property (nonatomic, strong) PAPSettingsActionSheetDelegate *settingsActionSheetDelegate;
#property (nonatomic, strong) UIView *blankTimelineView;
#end
#implementation PAPHomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"LoadView is called");
// Present Anypic UI
[self presentUI];
}
-(void) presentUI {
/*UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
[backgroundImageView setImage:[UIImage imageNamed:#"DefaultAnypic.png"]];
self.view = backgroundImageView;*/
// Settings button
self.settingsButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.settingsButton addTarget:self
action:#selector(settingsButtonAction:)
forControlEvents:UIControlEventTouchUpInside];
[self.settingsButton setTitle:#"Settings" forState:UIControlStateNormal];
self.settingsButton.frame = CGRectMake(200.0, 200.0, 100.0, 100.0);
[self.view addSubview:self.settingsButton];
}
and its .h file is this :
#interface PAPHomeViewController : PAPPhotoTimelineViewController
#end
The PAPPhotoTimelineViewController is also a separateViewController, which the Home extends from and it is a tableViewController, which also calls ViewDidLoad.
The problem :
With the above Code, I see my button and I can click on my button.
But, if I uncomment stuff for the background, I do see the background, I do see the button, but it cannot be clicked - it is like no touch on the button is identified.
I am also confused, now that I am extending another ViewController which also implements viewDidLoad, why they are both called, in which order etc.
You shouldn't assign a new UIView to your self.view.
Instead of self.view = backgroundImageView;, just add it like a random view.
[self.view addSubview:backgroundImageView];
Doing that, you will follow the right way to add subview: your backgroundImageView will be displayed in your view, and your button will be add above it.
I have a tableview controller class, a tableviewcell subclass and a uibutton subclass.
I am creating an instance of the unbutton subclass in the tableviewcell subclass and initialize a button in a specific cell position.
Then I am using this cell in the tableview controller class. Also I am trying to add an IBAction to the button. But it can't recognize the object of the uibutton subclass while everything else works fine. What am I doing wrong in the declaration?
tableviewcell.m
#import "tableviewcell.h"
#import "CustomCheckButton.h"
CustomCheckButton *starbtn = [[CustomCheckButton alloc] init];;
starbtn = [[CustomCheckButton alloc]initWithFrame:CGRectMake(243,0, 30, 30)];
tableviewcontroller.m
In the cellForRowAtIndexPath object startbtn won't be recognized:
#import "ScannedProductControllerViewController.h"
#import "imageCellCell.h"
tableviewcell*firstRowCell = (tableviewcell *)cell;
[firstRowCell.prodimage setImage: [UIImage imageNamed:#"test1.jpg"]];
[firstRowCell.label1 setText:#"17.5"];
[firstRowCell.label2 setText:#"Score"];
[firstRowCell.basket setImage: [UIImage imageNamed:#"Basket.jpg"]];
// reference of the home button to the buttonclick method
[firstRowCell.homebtn addTarget:self action:#selector(clickButton:) forControlEvents:UIControlEventTouchUpInside];
// reference of the favorites button to the buttonclick method
[firstRowCell.starbtn addTarget:self action:#selector(clickFavButton:) forControlEvents:UIControlEventTouchUpInside];
CustomCheckButton.h
#import <UIKit/UIKit.h>
#interface CustomCheckButton : UIButton {
BOOL _checked;
}
#property (nonatomic, setter = setChecked:) BOOL checked;
-(void) setChecked:(BOOL) check;
#end
#interface TableViewCell : UITableViewCell
#property (nonatomic, strong) CustomCheckButton *startButton;
#end
#implementation TableViewCell
- (instancetype)init {
self = [super init];
self.startButton = [[CustomCheckButton alloc]initWithFrame:CGRectMake(243,0, 30, 30)];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.contentView addSubview:self.startButton];
}
#end
First, why don't you create your cells in the storyboard as a prototype cell or inside of a XIB file.
Then, create an IBAction to the button and respond to it in the cell. Finally, make a protocol for your cell that will inform the delegate when the button is tapped. You can then setup your table view controller as the delegate of the cells and respond to the button tapped. If you need to determine which cell you're working with, you can even pass the cell as a parameter of one of the protocol's methods you use.
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.