I'm trying to find which action is triggered by a UIGestureRecognizer on which target. Unfortunately there is no property on a UIGestureRecognizer such as gesture.action or gesture.target. The gesture I'm analyzing is part of UIKit private implementation.
Partial Answer here
stackOverFlow Question 20066315
Here's a code snippet that will list all target/action pairs associated with a gesture recognizer:
Ivar targetsIvar = class_getInstanceVariable([UIGestureRecognizer class], "_targets");
id targetActionPairs = object_getIvar(gesture, targetsIvar);
Class targetActionPairClass = NSClassFromString(#"UIGestureRecognizerTarget");
Ivar targetIvar = class_getInstanceVariable(targetActionPairClass, "_target");
Ivar actionIvar = class_getInstanceVariable(targetActionPairClass, "_action");
for (id targetActionPair in targetActionPairs)
{
id target = object_getIvar(targetActionPair, targetIvar);
SEL action = (__bridge void *)object_getIvar(targetActionPair, actionIvar);
NSLog(#"target=%#; action=%#", target, NSStringFromSelector(action));
}
Note that you'll have to import <objc/runtime.h>, and that this uses private ivars and a class, so it could get you banned from the App Store.
I have a different solution to this which has worked for me. This is more of a design change... you cannot access the target from the captured gesture. So instead keep a reference to the object when the touch down happened and before the pan began.
#property (nonatomic, strong) UIButton *myTouchedButton; // reference to button
(void)init
{
...
[card.button addTarget:self action:#selector(cardTouchDownInside:) forControlEvents:UIControlEventTouchDown];
...
}
-(void)cardTouchDownInside:(id)sender
{
NSLog(#"touch down on object");
self.myTouchedButton = (UIButton*)sender;
}
Related
I have several buttons in one class and their actions are separate.
I created the instance of that class in another class, and I have the UIButton array in the 2nd class. I want to call each button's action programmatically. Is there any way to do this in iOS?
UIButton has a method to invoke the targets/selectors that are linked to a certain control event:
[button sendActionsForControlEvents:UIControlEventTouchUpInside];
2022 Swift syntax:
someButton.sendActions(for: .primaryActionTriggered)
For Swift 3.0/4.0/5.0:
button.sendActions(for: .touchUpInside)
You can simply call first class's action method as instance method from other classes by passing a UIbutton id
e.g.:
in first class "ClassA"
- (IBAction)classAbuttonAction1:(id)sender;
- (IBAction)classAbuttonAction2:(id)sender;
from another class
UIButton *bClassButton = [[UIButton alloc]init];
ClassA *instance = [[ClassA alloc]init];
[instance classAbuttonAction1:bClassButton];
[instance classAbuttonAction2:bClassButton];
I'm trying to have another object call a selector. I'm attempting to define this selector from another class by defining the selector property. It doesn't seem to be working like I expect.
ComboBox.h
#property (nonatomic) SEL onComboSelect;
ComboBox.m
-(void)doneClicked:(id) sender
{
[textField resignFirstResponder]; //hides the pickerView
NSLog(#"DONE CLICKED CALLED");
[self performSelector:#selector(onComboSelect)];
}
OtherClass.h
#interface OtherClass : BaseViewController
{
ComboBox *combo;
}
-(void)comboSelector;
OtherClass.m
// in viewDidLoad
combo = [[ComboBox alloc] init];
combo.onComboSelect = #selector(comboSelector);
-(void)comboSelector
{
NSLog(#"COMBO SELECTOR");
}
I see "DONE CLICK CALLED" in the logs, but not "COMBO SELECTOR". So I know doneClicked is being called, but the selector doesn't seem to be working. What am I doing wrong? Is there a better way to do this?
A #selector is just a method name - it does not include any context about the class on which it is defined. So this [self performSelector:#selector(onComboSelect)] is just invoking the method on self. In addition to the selector, you also need a reference to the object on which you want to call it.
Notice how some built-in classes (like UIControl) take both a target object and action selector.
There are a 2 major issues in your code.
1.
onComboSelect is a SEL so no need to use the #selector again.
Instead of:
[self performSelector:#selector(onComboSelect)];
Use :
[self performSelector:onComboSelect];
2.
You are calling the selector on self from ComboBox class, so it'll call the selector on ComboBox object (if defined) not on OtherClass object
Your answers were helpful. Here is what I did:
added to ComboBox.h
#property (nonatomic, weak) UIViewController *parentViewController;
added to ComboBox.m
-(void)doneClicked:(id) sender
{
[textField resignFirstResponder]; //hides the pickerView
if ([parentViewController respondsToSelector:#selector(comboSelector)])
[parentViewController performSelector:#selector(comboSelector)];
}
added to OtherClass.m
combo.parentViewController = self;
#property (nonatomic) SEL onComboSelect <-- This property is not needed in ComboBox.h.
SEL is a point of objc_seletor,and in object_seletor runtime can find the
objc_method ,that define
objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
and IMP is the point of method,and you can find this in "runtime.h" file.
Any noe kow how to find objc_method by object_seletor ? and I cannot find the define of objc_seletor struct.
I'm in the process of writing a fairly simple Math's puzzle game for one of my children.
I used the standard XCode SpriteKit Game template which creates an SKView with an SKScene and ViewController.m and MyScene.m files.
I then added a simple UIView to act as a container for a NumberPad 0-9 with UIButtons.
I'm tempted to target MyScene.m as the target for the IBActions as it will use the changes in state from the button presses.
However I'm wondering which class is a better target for the IBActions the ViewController or MyScene. In particular are there any performance implications to either choice.
My main concern is some articles I've seen about issues people have encountered when mixing SpriteKit and UIKit.
For your purposes the performance implications are completely negligible. However from a code point of view I would probably target the viewcontroller, in case you want to swap out the scene but reuse your numberpad. It sounds like a likely development in the future for a math type game.
I took Theis' advice and so my view controller, which creates the scene, contains the following code.
ViewController.m
- (IBAction)numberPressed:(id)sender {
UIButton *pressedButton = (UIButton*) sender;
NSString *button = pressedButton.currentTitle;
[scene.keysPressed addObject:button];
}
- (IBAction)clearPressed:(id)sender {
UIButton *pressedButton = (UIButton*) sender;
NSString *button = pressedButton.currentTitle;
[scene.keysPressed addObject:button];
}
And then in my scene code I have declared the keysPressed property, being somewhat paranoid I've made it atomic in case the view controller and scene ever run in different threads.
MyScene.h
#property (strong, atomic) NSMutableArray *keysPressed;
And then in the scenes update method I just check the mutable array I'm using as a stack to see anything has been added and get its value and delete it.
MyScene.m
-(void)update:(CFTimeInterval)currentTime {
...
NSString *keyNum = nil;
if([_keysPressed count] > 0) {
keyNum = [_keysPressed firstObject];
[_keysPressed removeObjectAtIndex:0];
}
So far everything is behaving itself.
I have printed a UITableviewCell's gesture in – tableView:didSelectRowAtIndexPath method in NSLog as
<UIScrollViewPanGestureRecognizer: 0x11e92080; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <UITableViewCellScrollView 0x11e94bf0>; target= <(action=handlePan:, target=<UITableViewCellScrollView 0x11e94bf0>)>>
and I have assigned this UIScrollViewPanGestureRecognizer to a UIGestureRecognizer to access the properties of it as follows,
UIGestureRecognizer *myGes=[temp.gestureRecognizers objectAtIndex:1];
I'm able to access all properties of 'myGes' as
myGes.state;
myGes.cancelsTouchesInView;
myGes.delaysTouchesEnded;
myGes.view;
Except one property named as target.
Is there any possibility to access that property? because i need to perform that action.
Any comments or suggestions would be appreciated.
Thank you in advance.
There is a way to gain access to the property target, but I'm not sure that this method will pass the Apple approval process.
NSMutableArray *targets = [myGes valueForKeyPath:#"_targets"];
id targetContainer = targets[0];//get first target for example
id targetOfMyGes = [targetContainer valueForKeyPath:#"_target"];
NSLog(#"%#", targetOfMyGes );//you can see reference for target object
Thanks neilco - his answer help create solution.
Note: the exact class of the object targetOfMyGes need to define yourself. By default it id - suitable for any object class.
UIGestureRecognizer internally maintains an array of targets. There is no public access to this array.
I have a different solution to this which has worked for me. This is more of a design change... you cannot access the target from the captured gesture. So instead keep a reference to the object when the touch down happened and before the pan began.
#property (nonatomic, strong) UIButton *myTouchedButton; // reference to button
(void)init
{
...
[card.button addTarget:self action:#selector(cardTouchDownInside:) forControlEvents:UIControlEventTouchDown];
...
}
-(void)cardTouchDownInside:(id)sender
{
NSLog(#"touch down on object");
self.myTouchedButton = (UIButton*)sender;
}
Im trying to make it so that every single UIControl in my application (UIButton, UISlider, etc) all have special extra properties that I add to them.
I tried to accomplish this by creating a UIControl Category and importing it where needed but I have issues.
Here is my code.
My setSpecialproperty method gets called but it seems to be getting called in an infinite loop until the app crashes.
Can you tell me what Im doing wrong or suggest a smarter way to add a property to all of my UIControls?
#interface UIControl (MyControl)
{
}
#property(nonatomic,strong) MySpecialProperty *specialproperty;
-(void)setSpecialproperty:(MySpecialProperty*)param;
#end
////////
#import "UIControl+MyControl.h"
#implementation UIControl (MyControl)
-(void)setSpecialproperty:(MySpecialProperty*)param
{
self.specialproperty=param;
}
///////////////
#import "UIControl+MyControl.h"
#implementation ViewController
UIButton *abutton=[UIButton buttonWithType:UIButtonTypeCustom];
MySpecialProperty *prop=[MySpecialProperty alloc]init];
[abutton setSpecialproperty:prop];
While you can't add an iVar to UIControl via a category, you can add Associated Objects, which can be used to perform much the same function.
So, create a category on UIControl like this:
static char kControlNameKey;
- (void) setControlName: (NSString *) name
{
objc_setAssociatedObject(self, &kControlNameKey, name, OBJC_ASSOCIATION_COPY);
}
- (NSString *) controlName
{
return (NSString *)objc_getAssociatedObject(array, &kControlNameKey);
}
There's more to it than that, I guess you'll need to check if an association exists before setting a new one, otherwise it will leak, but this should give you a start.
See the Apple Docs for more details
self.specialproperty=param is exactly the same as calling [self setSpecialproperty] (see here for some totally non biased coverage of Obj-C dot notation), which makes your current usage infinitely recursive.
What you actually want to do is:
-(void)setSpecialproperty:(MySpecialProperty*)param
{
_specialproperty = param;
}
Where _specialproperty is the implicitly created ivar for your property.
I'm assuming there's some reason why you've implemented your setSpecialproperty setter? Why not just use the one that is implicitly created for you?
the problem is that you can not add a property to a category, you can add behavior (methods) but not properties or attributes, this can only be done to extensions, and you can not create extensions of the SDK classes
use your method as
change your method name to
-(void)setSpecialproperty:(MySpecialProperty *)specialproperty
-(void)setSpecialproperty:(MySpecialProperty*)specialproperty
{
if(_specialproperty!=specialproperty)
_specialproperty = specialproperty;
}
and synthesize your specialProperty as
#synthesize specialproperty=_specialproperty;