Getting the Action of UIGestureRecognizer in iOS - ios

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;
}

Related

How to pass multiple parameters to a UITapGesture method with selector

Trying to figure out how to pass a string argument to my method which I call using a selector. It also happens to be a method I wrote to respond to a single Tap gesture
My Method looks like this :
-(void)handleSingleTap:(UITapGestureRecognizer *)recognizer Mystring:(NSString *) TheString{
}
I am trying to call the method like this :
UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleSingleTap:)];
Right now my call does not include the second NSString parameter I want to pass. How do I pass that second parameter? Thanks you.
Create category for UITapGestureRecognizer to use objc_setAssociatedObject
Add below category :
#import <objc/runtime.h>
static const void *stringKey = &stringKey;
#implementation UITapGestureRecognizer (string)
- (void)setString:(NSString *)stringToBePassedInGesture
{
objc_setAssociatedObject(self, stringKey, stringToBePassedInGesture, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)string
{
return objc_getAssociatedObject(self, stringKey);
}
#end
Use like this:
[singleTapGestureRecognizer setString:yourStringHere];
More reference from here
I have no idea what language have you come from (if any) but it does not work this way in objective-C. The object you create has a certain scope and can have an owner of sorts. That means if you created an object (your string) in a method viewDidLoad you can only use it in that method unless you assign it to some object (for instance to self using a property as already mentioned). I suggest you try to search the web about creating one of those.
In a situation as yours it would be great if the calling object could store your string as a property which could then be used in a handler method as well. That would mean you would assign the string to the tap gesture gesture.myString = myString and then in the handler you could call recognizer.myString to get this string. This can actually be achieved by subclassing the gesture recognizer and creating that property on it but I would not suggest doing something like that just to get a string passed.
So generally you can not do that the nice way and believe me I do wish it would be possible as this same issue can get extremely difficult is situations such as adding a button to a table view cell. The generic handles are very limited and using more or less anything such as buttons, cells, gesture recognizers you can not expect to get much more info then the sender itself (sometimes even less like an index path).

Find UIGestureRecognizer action (selector) name and target

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;
}

Adding a property to all of my UIControls

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;

How to set UISwitch on overlay?

I've set a switch on my overlay, which appears every time my app launches the camera. The switch appears, which is fine. But how do I create the if conditions to command the switch to perform an action when on or off?
//This is the overlay.
- (UIView*)CommomOverlay {
UISwitch *mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake (30,400,20,20)];
[mySwitch addTarget:self action:#selector(mySwitch)
forControlEvents:UIControlEventAllTouchEvents];
[view addSubview:mySwitch];
return view;
}
So the switch appears on the overlay, but how do I command it do something when called?
I've tried the following
-(void)mySwitch {
if ([mySwitch.on]){
execute this..
}
}
But the above doesn't work. I get an error saying "undeclared identifier, did you mean UISwitch?". So then I replace mySwitch.on with UISwitch.on, then it says " property on not found on object of type UISwitch".
I just want to execute my if else method properly. I made the overlay, I made the switch code and it appears on the overly perfectly. But now I want it to do something with an if/else condition. How do I rectify this?
What did I do wrong?
Glad you decided on the UISwitch, its a much more straightforward solution in my opinion. As for what your problem is, its related to scoping. You are declaring your UISwitch in your overlay initialization, and then the function ends, and you lose the ability to access your UISwitch.
What you'll need to do is create a property for it in your .h, then just set it equal to your property inside of your overlay initialization. That should fix your problem.
EDIT: Going through the problems you mentioned in the comment:
1) I believe that your declartion should be,
#property (nonatomic, retain) UISwitch *mySwitch;
Afterwards, you will need to do a #synthesize mySwitch; in your .m file
2) The reason for this is because you have 2 variables with the same name since it seems that you are redeclaring mySwitch in a few places. I'm guessing these places your code looks like:
UISwitch *mySwitch = //Stuff;
It should just look like:
mySwitch = //Stuff;
The reason being that you have already declared it in your .h file, you simply need to initialize or manipulate it. If you declare it once again, you will be overwriting it with a new instance that will once again not exist after you exit your function.
3) You dont need to set mySwitch = mySwitch for the synthesize, see my above code. Also make sure that your function name is not the same as your variable name!
Feel free to comment with updates and more questions.
-Karoly
mySwitch is not accessible in your mySwitch method. Create a property for mySwitch and create it's getter and setter. Then you can access it in your mySwitch method.
[..]
#property UISwitch *mySwitch;
[..]
#synthesize mySwitch = mySwitch;
-(void)mySwitch {
if ([self.mySwitch.on]){
[..]
}
}

alloc/init in viewDidLoad causes IB to ignore outlets

I just witnessed a very strange issue where my view would ignore all of the delegate calls coming from a custom view because I called alloc/init on the item at the load. I'm curious as to why.
#synthesize customTextField;
-(void)viewDidLoad {
// by calling this alloc/init, none of the changes here actually update to the view
// everything is ignored from here on in.
// if I comment out the alloc/init line, everything works fine
self.customTextField = [[UITextField alloc] init];
self.customTextField.text = #"Some text";
// setting font and size as well
}
While I would still get calls to the text field delegate methods, none were linked to my specific text fields. I could not respond to just customTextField.
I do realize that calling alloc/init will give me a completely new instance of customTextField... but why wouldn't that new instance be linked to IB and my view?
Because IB linking != binding.
When you link a variable in IB, it's a simply sets the variable once on first load, that's it. It does no other special code to track any changes to it, for good reason.
For example:
You are designing a UITableViewCell, and if you have a cell that is selected, you must rearrange all of the content inside the cell. In this case, you determined it would be easier if you just recreated all of the subviews and re-added them into the view, so you do the following:
-(void) layoutSubviews {
if (cellIsSelected)
{
// custom button is an IBOutlet property, which is by default a subview of self
self.customButton = [UIButton buttonWithType:UIButtonTypeCustom];
[[self someSubView] addSubview:customButton];
}
else {
// where is customButton located now? is it a subview of self or `someSubView`?
self.customButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// [self addSubview:customButton];
}
}
Thus, it is much easier for IB to say let's set this once, and let the programmer figure the rest out than for IB to try and track all changes made to an object and report them the to the UI.
viewDidLoad is called after your nib is loaded, and creating a new UITextField instance at this point will not be associated with your nib. If you're setting up new instances manually you also need to manually setup the delegates, and add them as subviews of your view.
The XIB file has no way of knowing that you are changing the reference. Consider the following piece of code
NSObject *myObjA = [[NSObject alloc]init]; //create object
NSObject *myObjB = myObjA; //assign reference <- this is the your case after xib load
myObjB = [[NSObject alloc]init]; //create object, myObjA still lives on.
It's basically the same that happens when you load your XIB file; You get the reference to the instantiated object (equals myObjB in above example). You can do with the reference what ever you please but you do not change the interface instance just by creating a new object.

Resources