Pass UIButton event from subview to superview - ios

I have a UIView like a menubarView with buttons.
I added this menubarView to another view (parentView).
I need to call a method on the parentView on TouchUpInside of menubar button.
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(x, y, width, height);
[button addTarget:self action:#selector(myLocalMethod:) forControlEvents:UIControlEventTouchDown];
[button setTitle:#"Call My Method" forState:UIControlStateNormal];
[self addSubview:button];
Solution:
In menuBarView I added this code:
- (void)myLocalMethod:(UIButton *)button
{
ParentView *parent = (ParentView *)self.superview;
[parent myMethod:button];
}

Try this in myMethod
[((<SuperViewName>)self.superview) <methodInSuperView>]
Replace the placeholders SuperViewName and methodInSuperView with your names.
Explanation: I am casting the superview to the appropriate superview class and the invoking the required superview method. Make sure the methodInSuperView is public (Declare it in interface).

One option is to provide callback blocks on the subview:
#property (nonatomic, copy) void(^onDidTapMyButton1)();
Then implement an action (or a target and selector) on the subview that calls this callback:
- (IBAction)didTapMyButton1 {
if (self.onDidTapMyButton1) {
self.onDidTapMyButton1();
}
}
In your parent view you can then do:
mySubView.onDidTapMyButton1 = ^{
// perform some action
};
You can also do this with a single callback for all buttons:
#property (nonatomic, copy) void(^onDidTapButton)(UIButton *tappedButton);
- (IBAction)didTapButton:(UIButton *)sender {
if (self.onDidTapButton) {
self.onDidTapButton(sender);
}
}

Related

How to invoke a method from UIViewController class to Custom UIView class

I am not able to call the method which is written in UIViewController(aViewController) from my custom view bView .
I want to call the method function on my button(btn) click in UIView(bView). But when I click the button, i am getting the following error.
unrecognized selector sent to instance....
Here is my code:
aViewController.h
#import "bView.h"
#import "cViewController.h"
-(void)function;
aViewController.m
-(void)viewDidLoad{
bView = [[bView alloc]init];
[bView makeView];
bView.bViewController = self;
[self.view addSubView:bView];
}
-(void)function{
cViewController *nextViewController = [[cViewController alloc] initWithNibName: nil bundle: nil];
[self.navigationController pushViewController: nextViewController animated: YES];
}
bView.h
#import "bViewController.h"
#interface bView : UIView{
UIButton btn;
bView *BView;
}
-(void)makeView;
-(void)function;
#property (nonatomic,readonly) UIButton *btn;
#property (assign) UIViewController* aViewController;
bView.m
#import "bView.h"
#import "aViewController.h"
#impementation bView
#synthesize btn;
#synthesize aViewController;
-(void)makeView{
btn= [UIButton buttonWithType:UIButtonTypeRoundedRect];
UIImage *btnimg = [UIImage imageNamed:#"btn.png"];
[btn setBackgroundImage:btnimg forState:UIControlStateNormal];
btn.frame = CGRectMake(65, 380, 60, 60);
[self addSubview:btn];
btn.userInteractionEnabled = YES;
[btn addTarget:aViewController action:#selector(function) forControlEvents:UIControlEventTouchUpInside];
}
How to solve it?
You are setting a target to button in [bView makeView]; method, but at that time bView's aViewController is nil because you are setting it only on the next line. So try this:
-(void)viewDidLoad{
bView = [[bView alloc]init];
bView.bViewController = self;
[bView makeView];
[self.view addSubView:bView];
}
this is how you do it, first off, get rid of the "bView.bViewController = self", it doesn't work that way, you shouldn't do this, ever.
-(void)viewDidLoad {
bView = [[bView alloc]init];
[bView makeView];
[self.view addSubView:bView];
[[bView btn] addTarget:self action:#selector(function) forControlEvents:UIControlEventTouchUpInside];
}
function is declared in your UIViewController, not your UIView, so you must call to the button in your viewdidLoad through the bView and then assign it a target and action. I still wouldn't use this method that you are using, but this will do it.
this is the magic:
[[bView btn] addTarget:self action:#selector(function) forControlEvents:UIControlEventTouchUpInside];
AND, remove these lines from the UIView:
#property (assign) UIViewController* aViewController;
-(void)function;
[btn addTarget:aViewController action:#selector(function) forControlEvents:UIControlEventTouchUpInside];
you should remove these if your intent is for the UIViewController to have the function "-(void)function" and I assume this is what you meant. I'm assuming that your "function" is supposed to be called in your UIViewController
for out purposes here, this:
[[bView btn] addTarget:self action:#selector(function) forControlEvents:UIControlEventTouchUpInside];
is the same thing as this:
[bView.btn addTarget:self action:#selector(function) forControlEvents:UIControlEventTouchUpInside];
and
bView.btn = #property (nonatomic,readonly) UIButton *btn;
In fact, you don't need to call this button in any UIView, given what you are showing, just call this in your viewDidLoad, like this:
-(void)viewDidLoad
{
[super viewDidLoad];
UIButton * btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
UIImage *btnimg = [UIImage imageNamed:#"btn.png"];
[btn setBackgroundImage:btnimg forState:UIControlStateNormal];
btn.frame = CGRectMake(65, 380, 60, 60);
btn.userInteractionEnabled = YES;
[self.view addSubview:btn];
[btn addTarget:self action:#selector(function) forControlEvents:UIControlEventTouchUpInside];
}
I'm showing you this because you aren't setting up the UIView correctly in the first place by invoking super on it's designated initializer so the best bet now is to just place the button in your viewDidLoad and move on, this is the solution until you understand how the initialize a UIView and set it up correctly. There's a lot of stuff that is wrong with the UIView you have set up and tried to subclass and it's going to be easier for you to just make the button in viewDidLoad and then learn how to subclass correctly in the future.

UIButton with action-selector method in different class

Related to this previous question (Creating UI elements programmatically in Objective-C), this method resides in one class of the project, and other classes are able to call it to programmatically create buttons:
UIButton *createButton(CGFloat x, CGFloat y, CGFloat width, CGFloat height, NSString *caption, NSTextAlignmentCenter textPosition, UIColor *textColor, UIColor *backColor) {
UIButton *control = [[UIButton alloc] initWithFrame: CGRectMake(x, y, width, height)];
[control setTitle:caption forState:UIControlStateNormal];
control.titleLabel.textAlignment = textPosition;
control.backgroundColor = backColor;
[control setTitleColor: textColor forState:UIControlStateNormal];
return control;
}
I am now trying to add the target and action methods, to be handled by a common function, like so:
[control addTarget:nil
action:#selector(commonHandler)
forControlEvents:UIControlEventTouchDown];
It works for buttons that reside in the same class as the createButton() function, but although the buttons are successfully created in the other classes as well, they don't trigger the commonHandler() method.
NOTE: the createButton() and commonHandler() methods have been declared in its header file, and imported by all classes.
Is there any way to achieve this?
EDIT: The proposed solution still crashes
I modified the code as per the suggestions, and converted both the createButton and buttonAction to Objective-C methods, to enable reference to self.
class1 header
// class1.h
#import <UIKit/UIKit.h>
#import "class2.h"
#interface class1 : UIViewController {
UIButton *button1;
}
- (void) buttonAction:(UIButton *)control;
- (UIButton *) createButtonWithxPos:(CGFloat)x
yPos:(CGFloat)y
width:(CGFloat)width
height:(CGFloat)height
caption:(NSString *)caption
textPos:(NSTextAlignment)textPosition
textClr:(UIColor *)textColor
backClr:(UIColor *)backColor;
#end
class1 implementation
// class1.m
#import "class1.h"
#import "class2.h"
#implementation class1
- (void)viewDidLoad {
[super viewDidLoad];
button1 = [self createButtonWithxPos:10
yPos:10
width:200
height:30
caption:#"Cls1 Button"
textPos:NSTextAlignmentCenter
textClr:[UIColor whiteColor]
backClr:[UIColor blackColor]];
[self.view addSubview: button1];
}
- (UIButton *) createButtonWithxPos:(CGFloat)x
yPos:(CGFloat)y
width:(CGFloat)width
height:(CGFloat)height
caption:(NSString *)caption
textPos:(NSTextAlignment)textPosition
textClr:(UIColor *)textColor
backClr:(UIColor *)backColor
{
UIButton *control = [[UIButton alloc] initWithFrame: CGRectMake(x, y, width, height)];
[control setTitle:caption forState:UIControlStateNormal];
control.titleLabel.textAlignment = textPosition;
control.backgroundColor = backColor;
[control setTitleColor: textColor forState:UIControlStateNormal];
[control addTarget:self action: #selector(buttonAction:) forControlEvents:UIControlEventTouchDown];
return control;
}
- (void) buttonAction:(UIButton *)control {
NSLog(#"button clicked...");
}
#end
class2 header
// class2.h
#import <UIKit/UIKit.h>
#interface class2 : UIViewController {
UIButton *button2;
}
#end
class2 implementation
// class2.m
#import "class1.h"
#import "class2.h"
#implementation class2
- (void) viewDidLoad {
[super viewDidLoad];
class1 *cls1 = [[class1 alloc] init];
button2 = [cls1 createButtonWithxPos:10
yPos:10
width:200
height:30
caption:#"Cls2 Button"
textPos:NSTextAlignmentCenter
textClr:[UIColor whiteColor]
backClr:[UIColor blackColor]];
[self.view addSubview: button2];
}
#end
Both button1 and button2 are created properly, but button1 triggers the buttonAction method while button2 does not - it crashes on touch.
Is it implemented wrongly?
(please excuse any omissions as I have only extracted the relevant portions from the actual project to illustrate here)

understanding uibutton target self superview

Can someone please explain to me the uibutton target functionality from this example:
I have a ViewController. I add a uiview with two buttons to this viewcontroller. One button is crated in the init and the other one is created by a method 'addSecondButton'. Both buttons have the same action on [self superview] target which is the ViewController. The code:
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
MySpecialView *myspecialview = [[MySpecialView alloc] initWithFrame:self.view.frame];
[self.view addSubview:myspecialview];
[myspecialview addSecondButton];
}
- (void)specialMethod { NSLog(#"right here!"); }
MySpecialView.m
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 30, frame.size.width, 50)];
[button addTarget:[self superview] action:#selector(specialMethod) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor blueColor];
[self addSubview:button];
}
return self;
}
- (void)addSecondButton {
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 90, self.frame.size.width, 50)];
[button addTarget:[self superview] action:#selector(specialMethod) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor redColor];
[self addSubview:button];
}
So when tapping the blue button which is created in the init, specialMethod does execute. When pressing the red one which is added after the init, the app crashes with the warning - unknow selector for uiview.
What I really don't understand is when I NSLog [self superview] in the init, it is null, because the object is not yet created and returned to the superview, but the action does get executed for some reason.
Thanks for the help!
You have indeed added a nil target for the blue button in initWithFrameMethod. According to the Apple's Event Handling Guide, when you add a nil target, the message (specialMethod in your case) will be passed through the responder chain:
When the user manipulates a control, such as a button or switch, and the target for the action method is nil, the message is sent through a chain of responders starting with the control view.
As your ViewController is a part of the responder chain, it will receive this message and call its specialMethod.
[self superview] is a pointer to ViewController's view, not ViewController itself. The selector you are defining as the action for the button should target the instance of ViewController, not the ViewController's view since you have the method specialMethod defined in ViewController. If you really wanted to use [self superview] as the target for the action, then you'll need to subclass UIView, implement specialMethod and set that new UIView subclass as the view of the view controller (not a subview). This way, [self superview] will refer to a class that actually has the selector specified on the target.
I didn't actually try your code sample, but it's so obviously wrong, if it does work, it's coincidence and shouldn't be relied upon.

Reuse UIButton style

I've got a couple of UIButtons with all the same style, but sometimes they have different colors and sometimes they have different widths. However, I hate to make a seperate class for a new button that just needs a slightly different color, so I thought that there must be a way to style a button and take, for instance, the color and height as a property. After that I image it would work just as importing a UITableView's datasource/delegate.
So a more concrete question: is there a way to make a class for a UIButton that takes parameters such as width and height, and is it possible to declare this kind of class to a UIButton?
That's no problem at all. Create a category or subclass of UIButton. Define a custom init method like - (instancetype)initWithColor:(UIColor *)color size:(CGSize)size. In the init method call self = [super initWithFrame:CGRectZero] and then set those properties on self.
About the frequently used style, you can make a method which return a styled button.
- (UIButton *)styledButtonWithFrame:(CGRect)frame
{
UIButton *btn = [[UIButton alloc] initWithFrame:frame];
[btn setBackgroundColor:[UIColor redColor]];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
return btn;
}
and you can create an instance of styledButton
UIButton *styledBtn = [self styledButtonWithFrame:CGRectMake(0, 0, 10, 10)];
The second question, you can use category.
#interface UIView (BKExpansion)
#property (nonatomic, assign) CGFloat frameX;
#property (nonatomic, assign) CGFloat frameHeight;
#end
#implementation UIView (BKExpansion)
- (CGFloat)frameX
{
return self.frame.origin.x;
}
- (void)setFrameX:(CGFloat)frameX
{
CGRect frame_ = self.frame;
frame_.origin.x = frameX;
self.frame = frame_;
}
- (CGFloat)frameWidth
{
return self.frame.size.width;
}
- (void)setFrameWidth:(CGFloat)frameWidth
{
CGRect frame_ = self.frame;
frame_.size.width = frameWidth;
self.frame = frame_;
}
#end
You can add frameY, frameBottom, and so on.
If you want to change frameX or frameWidth, just use
[styledBtn setFrameY:30];

UnEven Behaviour of UIView when setHidden

I've created 5 UIView dynamically, which consists of one UILabel and one UIButton each. When I click button, the UIView will setHidden. But it works only on one not other four uiviews.
#interface ViewController : UIViewController
{
NSMutableArray *newViews;
}
#property(nonatomic,retain)IBOutlet UILabel *welcome;
#property(nonatomic,retain)CustomView *custom;
-(void)buttonPressed:(UIButton *)sender;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *name=#"string of length";
int length=[name length];
newViews = [NSMutableArray array];
NSMutableArray *myArray = [NSMutableArray arrayWithObjects:#"cricket", #"golf",#"wrestling", #"FootBall is good game", nil];
int yAxis=44;
int lengthOfArray=[myArray count];
for(int a=0; a<=lengthOfArray; a++){
self.custom= [[CustomView alloc]initWithFrame:CGRectMake(20, yAxis, 100, 44)];
yAxis=yAxis+50;
NSLog(#"yaxis is %i",yAxis);
self.custom.tag=200+a;
[newViews addObject:self.custom];
self.custom.Label = [[UILabel alloc]initWithFrame:CGRectMake(5,5, length+70, 30)];
self.custom.button=[[UIButton alloc]initWithFrame:CGRectMake(85,10,12,10)];
UIImage *btnImage = [UIImage imageNamed:#"button_droparrow.png"];
[self.custom.button setImage:btnImage forState:UIControlStateNormal];
[self.custom.button addTarget:self action:#selector(buttonPressed:)forControlEvents:UIControlEventTouchDown];
self.custom.button.tag=self.custom.button.tag+a;
self.custom.backgroundColor=[UIColor greenColor];
custom.Label.text=#"welcome";
custom.Label.backgroundColor = [UIColor yellowColor];
[self.custom addSubview:self.custom.button];
[self.custom addSubview:custom.Label];
[self.view addSubview:self.custom];
}
[self.custom.button addTarget:self action:#selector(buttonPressed:)forControlEvents:UIControlEventTouchDown];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(void)buttonPressed:(UIButton *)sender
{
[self.custom setHidden:YES];
}
#end
Kindly help me. I am new in iOS development. I need here to create UIView with differrnt reference and that reference assign to UIButton to close that particular UIView but I could not get result out.
You could use UISegmentedControl along with number of xib for each UIView.
In each UIView you can place the required UIControls and link the same.
In the delegate method of SegmentedControl 'indexDidChangeForSegmentedControl:(UISegmentedControl *)sender' on each index remove the earlier UIView and add the required UIView.
In the main header file add the IBOutlet for each UIView
#property (nonatomic, weak) IBOutlet UIView *view1;
#property (nonatomic, weak) IBOutlet UIView *view2;
In .m file in the delegate method 'indexDidChangeForSegmentedControl'
- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl *)sender {
NSUInteger index = sender.selectedSegmentIndex;
if (UISegmentedControlNoSegment != index) {
if (currentIndex == index) {
return;
}
currentIndex = index;
switch (index) {
case 0:
{
[self.previousView removeFromSuperview];
[self.view addSubview:view1];
self.previousView = view1;
}
break;
case 1:
{
[self.previousView removeFromSuperview];
[self.view addSubview:view2];
self.previousView = view2;
}
break;
}
}
}
Hope this helps.
If you want to use properties, you will have to make a property for each view. Instead, if you want to create them dynamicaly you could store the references to each view in an array.
The next you should know/do is to add a tag to each button. A tag is just a number, which in this case should reference to its position in the Array.
Then based on the button tag (that you can retrieve from the sender) you can retrieve the proper view/button from the array and change the Hidden property on it.
For example (pseudo code/this wont compile):
Creating the views array
#property (nonatomic, strong) NSMutableArray *views;
In View did load create the views
views = [[NSMutableArray alloc] init];
int nrOfViews = 5;
for(int a=0; a<=nrOfViews; a++){
UIView *view = create UIView here.
UIButton *button = create button here.
[view addSubView: button];
[button setTag: a];
[views addObject: view];
}
reference to the view through the pointer retained in the array, find the right one based on the button tag.
-(void)buttonPressed:(UIButton *)sender
{
UIView *view = [views objectAtIndex: sender.tag]; //using the button tag to identify the right view.
[view setHidden: yes];
}
Try something like this:
- (void) buttonPressed: (UIButton*) sender
{
UIView* view = sender.superview;
view.hidden = YES;
}
You need to make some changes as follows
#property(nonatomic,strong)IBOutlet UILabel *welcome; // new arc code
#property(nonatomic,strong)UIView *custom; // new arc code
self.custom = [[UIView alloc]initWithFrame:CGRectMake(20, yAxis, 100, 44)];

Resources