I am creating an iOS app as defined here! So here in this app I am using a segmented control (Dynamically added) to give the user the choice to select one of the options. When the user selects a choice I want to send a string to selector method, so I wanted to know if we can send anything apart from id and event to an action:#selector that we add.
Here's the code
NSArray *optionsArray =[NSArray arrayWithArray:qna2.answers];
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:optionsArray];
segmentedControl.frame = CGRectMake(xOffset,yOffset, 250, 50);
[segmentedControl setSelectedSegmentIndex:UISegmentedControlNoSegment];
[self.view addSubview:segmentedControl];
yOffset+=100;
[segmentedControl addTarget:self action:#selector(MyAction:) forControlEvents: UIControlEventValueChanged];
So can we do like #selector(Myaction: Category) ?
Your best bet is probably to store the string in a property like this:
#property (nonatomic, copy) NSString *myString;
Then, retrieve the value in the myAction: method.
Or you could subclass your control and add a property that way:
#interface MySegmentedControl : UISegmentedControl
#property (nonatomic, copy) NSString *stringProperty;
#end
When you set each segmented control:
MySegmentedControl *segmentedControl = [[MySegmentedControl alloc] initWith...];
segmentedControl.stringProperty = #"Some meaningful value";
[segmentedControl addTarget:self action:#selector(myAction:) forControlEvents:UIControlEventValueChanged];
In the change handler:
- (void) myAction:(MySegmentedControl *)sender
{
NSString *importantString = sender.stringProperty;
// do stuff with importantString
}
My approach would be to subclass UISegmentedControl, say MYUISegementedControl, and create a delegate protocol so that it can call back to your UIViewController.
When you create your MYUISegmentedControl instance you can add whatever additional properties you need and pass this information back to the delegate, or at the very least your delegate can access these properties off the MYUISegmentedControl properties.
You could also use a category rather than a subclass.
Related
Ok, so till now i have been declaring all my view objects in .h file. For e.g:
UIButton *btnCustomFacebookLogin;
And define in my .m file like:
btnCustomFacebookLogin = [UIButton buttonWithType:UIButtonTypeCustom];
btnCustomFacebookLogin.backgroundColor = [UIColor darkGrayColor];
btnCustomFacebookLogin.frame = CGRectMake(0,0,180,40);
btnCustomFacebookLogin.center = CGPointMake(self.view.center.x, 200);
[btnCustomFacebookLogin setTitle: #"Facebook Login" forState: UIControlStateNormal];
[btnCustomFacebookLogin addTarget:self action:#selector(facebookLoginButtonClicked) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview:btnCustomFacebookLogin];
And i have a few more buttons in my app which are all defined in my
- (void)viewDidLoad
I got this checked from my mentor and he told me that all these buttons should be in a separate method and not in viewDidLoad, i have no idea where they should be, i went through some sample codes on the internet and could not find a clue. What is the proper place where all the buttons are defined ? Since i work for a company now i have to follow what conventions are told to me.
Keep your IBOutlets properties outside of header file (*.h) and declare them inside implementation file (*.m) in interface extension of your View Controller.
In your implementation file (*.m) :
#interface MyViewController ()
#property (nonatomic, weak) IBOutlet UIButton *myButton;
#end
Remember to set your property behaviour nonatomic as by default each property gets atomic behaviour and when using UI objects there is no need to make them thread safe. Set it to weakas you are not a direct owner of the object, its lifecycle belongs to your storyboard (assuming you use storyboard).
Meet Doshi is correct about using viewDidLoad and performing selector from inside it, to make an initial setup for your UI elements. It makes your code clear and readable.
I would possibly call it:
- (void)styleUI {
//insert your code here
}
If you find yourself to set specific UIButton styling in different View Controllers in this same way, then to avoid boilerplate code you could create own Class Category for UIButton and specify this same style in one place.
Create a new file selecting Objective-C File.
In File type prefix for your project with name for class (example:
MAPStyling).
Select Category for File Type.
Select Class that you want to create a category for (for UIButton select UIButton).
Inside you category class in implementation file (*.m) add method for your styling:
- (void)map_applyFacebookStyle
// insert your code here but instead to name of your button refer to self.
self.title = #"Facebook Login";
}
Expose method in header file of your category by adding method signature into #interface section.
#interface UIButton (MAPStylig)
- (void)map_applyFacebookStyle;
#end
In your View Controller implementation file (*.m) import your Class Category and modify your method:
- (void)styleUI {
[btnCustomFacebookLogin map_applyFacebookStyle];
}
This will call #selector of your styling method from your Class Category on your UIButton. As you can imagine in this way you can remove a lot of boilerplate code from every View Controller in your application.
Your mentor says, "All these buttons should be in a separate method and not in viewDidLoad". Means you are setting frames and properties to all buttons into viewDidLoad method. He didn't said that you can't define all objects into .h file. So he says, you have do that into other method. Just use following code.
I think, right now you did this:-
- (void)viewDidLoad
{
btnCustomFacebookLogin = [UIButton buttonWithType:UIButtonTypeCustom];
btnCustomFacebookLogin.backgroundColor = [UIColor darkGrayColor];
btnCustomFacebookLogin.frame = CGRectMake(0,0,180,40);
btnCustomFacebookLogin.center = CGPointMake(self.view.center.x, 200);
[btnCustomFacebookLogin setTitle: #"Facebook Login" forState: UIControlStateNormal];
[btnCustomFacebookLogin addTarget:self action:#selector(facebookLoginButtonClicked) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview:btnCustomFacebookLogin];
}
So, just put this code into one method and call this method from viewDidLoad like this:-
- (void)viewDidLoad
{
[self settingPropertiesOfButtons];
}
- (void)settingPropertiesOfButtons
{
//insert your code here..
btnCustomFacebookLogin = [UIButton buttonWithType:UIButtonTypeCustom];
btnCustomFacebookLogin.backgroundColor = [UIColor darkGrayColor];
btnCustomFacebookLogin.frame = CGRectMake(0,0,180,40);
btnCustomFacebookLogin.center = CGPointMake(self.view.center.x, 200);
[btnCustomFacebookLogin setTitle: #"Facebook Login" forState: UIControlStateNormal];
[btnCustomFacebookLogin addTarget:self action:#selector(facebookLoginButtonClicked) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview:btnCustomFacebookLogin];
}
That's it. If you could framing those buttons in .storyboard file or in .xib, then its better.
I know that this question has been asked so many times but i want to pass a custom object as an argument on clicking a button.
UIButton addTarget:action:forControlEvents: doesn't allow us to do that but it is important for me so I can do further on the basis of custom objects.
If an alternative for add target is possible, so please give the solution.
The code is like this:
Custom Object:
HeaderData *cell ;
Button:
_forward =[UIButton buttonWithType:UIButtonTypeRoundedRect];
[_forward setTitle:#"F" forState:UIControlStateNormal];
_forward.frame = CGRectMake(300, (_CELL_HEIGHT-_LABEL_HEIGHT)/2, 10 ,_LABEL_HEIGHT);]
[_forward addTarget:web action:#selector(functionName:) forControlEvents:UIControlEventTouchUpInside];
and the function which is called on clicking the UIButton _forward is:
-(void)functionName:(UIButton *)sender{
//code for further programming on the basis of cell.
}
You could make a custom subclass of UIButton:
#interface CustomDataButton : UIButton
#property (nonatomic, strong) id userData;
#end
Then in functionName, you can pull the data back out:
-(void)functionName:(UIButton *)sender
{
if ([sender isKindOfClass:[CustomDataButton class]])
{
id customData = ((CustomDataButton *) sender).userData;
}
}
caveat: written without an IDE. Watch out for typos etc.
I've got a list of values coming from a database, which each has it's own unique id. I want to be able to delete a row from the list using that id. My issue is, I'm trying to understand how to send a value through the button action to be used in the called function.
For ex:
NSString *sId = [_idArray objectAtIndex:i];
_fId = [sId intValue];
[deleteBtn addTarget:self action:#selector(deleteFeed:_fId) forControlEvents:UIControlEventTouchUpInside];
The _fId is the value I'm trying to understand how to send to the function deleteFeed. I know it must be something simple, but I just can't pin it down when searching Google.
addTarget has defined set of params and you cannot send custom.
As a workaround you can do below:
Use the following api and set the _fId as TAG to the button:
action:#selector(deleteFeed:)
i.e.
[deleteBtn setTag:_fId]
[deleteBtn addTarget:self action:#selector(deleteFeed:) forControlEvents:UIControlEventTouchUpInside];
Now retrieve the tag from the button from the associated tag to identify the button.
- (void) deleteFeed:(UIButton*)sender{
[self deleteWithTag:sender.tag];
// Or place opening logic right here
}
I hope this helps.
What about subclassing UIButton? This way you'll be able to name the property something appropriate instead of reusing the ambiguous tag.
#interface ButtonWithData : UIButton
#property (assign) int aValue;
#end
- (void)yourForLoopFunction {
for (NSString *sId in _idArray) {
NSString *sId = [_idArray objectAtIndex:i];
ButtonWithData *deleteBtn = [ButtonWithData buttonWithType:UIButtonTypeCustom];
deleteBtn.aValue = [sId intValue];
[deleteBtn addTarget:self action:#selector(deleteFeed:) forControlEvents:UIControlEventTouchUpInside];
[someView addSubview:deleteBtn];
}
}
This will only work if you create a new button for each array item that you want to delete. Without more code context, I can't create a better example for you to follow, unfortunately.
I am mocking up a quick demo of a project but am having a problem with a UITextField.
The behavior that we want is that when a user clicks on a button, there should be a custom view that appears with a UITextField and a UIButton in a custom view that overlays the main view.
I have a custom view called Searchview and the following in the Searchview.m. The problem is that when the textField is a property, it doesn't show but when it is a local variable, it does show. Can anybody help me with what is going on so that the UITextField shows? Is how I am doing this even the right idea (custom UIView or custom UIControl or a modal controller)? Finally, would setNeedsDisplay be appropriate here?
thx in advance
#interface Searchview()
#property (nonatomic, weak) UITextField *textField;
#end
- (void)drawRect:(CGRect)rect
{
// this doesn't work
self.textField = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 120.0f, 25.0f)];
self.textField.returnKeyType = UIReturnKeyDone;
self.textField.placeholder = #"Writer";
self.textField.borderStyle=UITextBorderStyleBezel;
[self.textField addTarget:self
action:#selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit];
[self addSubview: self.textField];
/* this works
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 120, 25)];
textField.returnKeyType = UIReturnKeyDone;
textField.placeholder = #"Writer";
textField.borderStyle=UITextBorderStyleBezel;
[textField addTarget:self
action:#selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit];
[self addSubview: textField];
*/
UIButton *mButton=[UIButton buttonWithType:UIButtonTypeRoundedRect];
mButton.frame=CGRectMake(200.0f,10.0f,100.0f,37.0f);
[mButton setTitle:#"search" forState:UIControlStateNormal];
[mButton addTarget:self action:#selector(showSearchController:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:mButton];
[self setNeedsDisplay];
}
As a property - not showing:
As a local variable - showing:
#property (nonatomic, strong) UITextField *textField;
change the weak to strong and change the self.textFiled to _textField to have a try
And make sure your textField property not be released
It's pretty simple when you think about it. ARC (approximately) converts the following code:
self.weakProp = [[Foo alloc] init];
to the equivalent of the following "manually reference-counted" code:
Foo * temp = [[Foo alloc] init];
self.weakProp = temp;
[temp release];
Nothing is retaining it, so it is released.
I can only think of two reasons to have assign/weak IBOutlets:
For an outlet in a VC, so it doesn't retain a subview when its view is set to nil (e.g. on a memory warning). This is less relevant in iOS 6.0 since views are not automatically released on a memory warning (so if you do it, you can release them all explicitly).
For a view where the outlet points to a superview (and would cause a retain cycle). This is quite rare.
In general, I prefer strong IBOutlets: They might keep objects alive for a little longer than necessary, but they are safer than assign and more efficient than weak. Just watch out for retain cycles!
I am using addTarget:action:forControlEvents like this:
[newsButton addTarget:self
action:#selector(switchToNewsDetails)
forControlEvents:UIControlEventTouchUpInside];
and I would like to pass parameters to my selector "switchToNewsDetails".
The only thing I succeed in doing is to pass the (id)sender by writing:
action:#selector(switchToNewsDetails:)
But I am trying to pass variables like integer values. Writing it this way doesn't work :
int i = 0;
[newsButton addTarget:self
action:#selector(switchToNewsDetails:i)
forControlEvents:UIControlEventTouchUpInside];
Writing it this way does not work either:
int i = 0;
[newsButton addTarget:self
action:#selector(switchToNewsDetails:i:)
forControlEvents:UIControlEventTouchUpInside];
Any help would be appreciated :)
action:#selector(switchToNewsDetails:)
You do not pass parameters to switchToNewsDetails: method here. You just create a selector to make button able to call it when certain action occurs (touch up in your case). Controls can use 3 types of selectors to respond to actions, all of them have predefined meaning of their parameters:
with no parameters
action:#selector(switchToNewsDetails)
with 1 parameter indicating the control that sends the message
action:#selector(switchToNewsDetails:)
With 2 parameters indicating the control that sends the message and the event that triggered the message:
action:#selector(switchToNewsDetails:event:)
It is not clear what exactly you try to do, but considering you want to assign a specific details index to each button you can do the following:
set a tag property to each button equal to required index
in switchToNewsDetails: method you can obtain that index and open appropriate deatails:
- (void)switchToNewsDetails:(UIButton*)sender{
[self openDetails:sender.tag];
// Or place opening logic right here
}
To pass custom params along with the button click you just need to SUBCLASS UIButton.
(ASR is on, so there's no releases in the code.)
This is myButton.h
#import <UIKit/UIKit.h>
#interface myButton : UIButton {
id userData;
}
#property (nonatomic, readwrite, retain) id userData;
#end
This is myButton.m
#import "myButton.h"
#implementation myButton
#synthesize userData;
#end
Usage:
myButton *bt = [myButton buttonWithType:UIButtonTypeCustom];
[bt setFrame:CGRectMake(0,0, 100, 100)];
[bt setExclusiveTouch:NO];
[bt setUserData:**(insert user data here)**];
[bt addTarget:self action:#selector(touchUpHandler:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:bt];
Recieving function:
- (void) touchUpHandler:(myButton *)sender {
id userData = sender.userData;
}
If you need me to be more specific on any part of the above code — feel free to ask about it in comments.
Need more than just an (int) via .tag? Use KVC!
You can pass any data you want through the button object itself (by accessing CALayers keyValue dict).
Set your target like this (with the ":")
[myButton addTarget:self action:#selector(buttonTap:) forControlEvents:UIControlEventTouchUpInside];
Add your data(s) to the button itself (well the .layer of the button that is) like this:
NSString *dataIWantToPass = #"this is my data";//can be anything, doesn't have to be NSString
[myButton.layer setValue:dataIWantToPass forKey:#"anyKey"];//you can set as many of these as you'd like too!
*Note: The key shouldn't be a default key of a CALayer property, consider adding a unique prefix to all of your keys to avoid any issues arising from key collision.
Then when the button is tapped you can check it like this:
-(void)buttonTap:(UIButton*)sender{
NSString *dataThatWasPassed = (NSString *)[sender.layer valueForKey:#"anyKey"];
NSLog(#"My passed-thru data was: %#", dataThatWasPassed);
}
Target-Action allows three different forms of action selector:
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
I made a solution based in part by the information above. I just set the titlelabel.text to the string I want to pass, and set the titlelabel.hidden = YES
Like this :
UIButton *imageclick = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
imageclick.frame = photoframe;
imageclick.titleLabel.text = [NSString stringWithFormat:#"%#.%#", ti.mediaImage, ti.mediaExtension];
imageclick.titleLabel.hidden = YES;
This way, there is no need for a inheritance or category and there is no memory leak
I was creating several buttons for each phone number in an array so each button needed a different phone number to call. I used the setTag function as I was creating several buttons within a for loop:
for (NSInteger i = 0; i < _phoneNumbers.count; i++) {
UIButton *phoneButton = [[UIButton alloc] initWithFrame:someFrame];
[phoneButton setTitle:_phoneNumbers[i] forState:UIControlStateNormal];
[phoneButton setTag:i];
[phoneButton addTarget:self
action:#selector(call:)
forControlEvents:UIControlEventTouchUpInside];
}
Then in my call: method I used the same for loop and an if statement to pick the correct phone number:
- (void)call:(UIButton *)sender
{
for (NSInteger i = 0; i < _phoneNumbers.count; i++) {
if (sender.tag == i) {
NSString *callString = [NSString stringWithFormat:#"telprompt://%#", _phoneNumbers[i]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:callString]];
}
}
}
As there are many ways mentioned here for the solution, Except category feature .
Use the category feature to extend defined(built-in) element into your
customisable element.
For instance(ex) :
#interface UIButton (myData)
#property (strong, nonatomic) id btnData;
#end
in the your view Controller.m
#import "UIButton+myAppLists.h"
UIButton *myButton = // btn intialisation....
[myButton set btnData:#"my own Data"];
[myButton addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
Event handler:
-(void)buttonClicked : (UIButton*)sender{
NSLog(#"my Data %#", sender. btnData);
}
You can replace target-action with a closure (block in Objective-C) by adding a helper closure wrapper (ClosureSleeve) and adding it as an associated object to the control so it gets retained. That way you can pass any parameters.
Swift 3
class ClosureSleeve {
let closure: () -> ()
init(attachTo: AnyObject, closure: #escaping () -> ()) {
self.closure = closure
objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
}
#objc func invoke() {
closure()
}
}
extension UIControl {
func addAction(for controlEvents: UIControlEvents, action: #escaping () -> ()) {
let sleeve = ClosureSleeve(attachTo: self, closure: action)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
}
}
Usage:
button.addAction(for: .touchUpInside) {
self.switchToNewsDetails(parameter: i)
}
There is another one way, in which you can get indexPath of the cell where your button was pressed:
using usual action selector like:
UIButton *btn = ....;
[btn addTarget:self action:#selector(yourFunction:) forControlEvents:UIControlEventTouchUpInside];
and then in in yourFunction:
- (void) yourFunction:(id)sender {
UIButton *button = sender;
CGPoint center = button.center;
CGPoint rootViewPoint = [button.superview convertPoint:center toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rootViewPoint];
//the rest of your code goes here
..
}
since you get an indexPath it becames much simplier.
See my comment above, and I believe you have to use NSInvocation when there is more than one parameter
more information on NSInvocation here
http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html
This fixed my problem but it crashed unless I changed
action:#selector(switchToNewsDetails:event:)
to
action:#selector(switchToNewsDetails: forEvent:)
I subclassed UIButton in CustomButton and I add a property where I store my data. So I call method: (CustomButton*) sender and in the method I only read my data sender.myproperty.
Example CustomButton:
#interface CustomButton : UIButton
#property(nonatomic, retain) NSString *textShare;
#end
Method action:
+ (void) share: (CustomButton*) sender
{
NSString *text = sender.textShare;
//your work…
}
Assign action
CustomButton *btn = [[CustomButton alloc] initWithFrame: CGRectMake(margin, margin, 60, 60)];
// other setup…
btnWa.textShare = #"my text";
[btn addTarget: self action: #selector(shareWhatsapp:) forControlEvents: UIControlEventTouchUpInside];
If you just want to change the text for the leftBarButtonItem shown by the navigation controller together with the new view, you may change the title of the current view just before calling pushViewController to the wanted text and restore it in the viewHasDisappered callback for future showings of the current view.
This approach keeps the functionality (popViewController) and the appearance of the shown arrow intact.
It works for us at least with iOS 12, built with Xcode 10.1 ...