Differences in catching touch: UIControl vs UIGestureRecognizer - iphone

I have custom class, which has UIView. I want it to do action, when the view is tapped. When i add:
[(UIControl*)customClass addTarget:self
action:#selector(doAction:)
forControlEvents:UIControlEventAllTouchEvents];
"doAction" is fireing sometimes when i click the view, however mostly not. But I've noticed that if i add:
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doAction:)];
[(UIControl*)customClass addGestureRecognizer:tapGesture];
What is the difference between these two recognizers? I know, that UIControl has delegates and UIGestureRecognizer, but can't understand why control isn't always catching touch.
The problem is that my sender in case of gestureRecognizer is wrong, because i can't pull some data from it.
- (void)doAction:(customClass*)sender {
switch (sender.actionType) { // do something ... } }
How to access it? Structure shown in breakpoint is like this:
sender -> UIGestureRecognizer -> _view -> _actionType

Related

Apply PanGesture to uiview in another class with action

I have a ViewController iDragHomeViewController
and another
NSObject class iDrag
"iDragHomeViewController.m"
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *dragView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
[dragView setBackgroundColor:[UIColor greenColor]];
[self.view addSubview:dragView];
iDrag *drag = [[iDrag alloc]init];
[drag makeDraggableView:dragView];
}
"iDrag.m"
-(void)makeDraggableView: (UIView *)dragView {
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(cellPan:)];
[dragView addGestureRecognizer:panRecognizer];
}
- (void)cellPan:(UIPanGestureRecognizer *)iRecognizer {
UIView *viewToDrag = [[UIView alloc]init];
viewToDrag = iRecognizer.view;
CGPoint translation = [iRecognizer translationInView:[viewToDrag superview]];
viewToDrag.center = CGPointMake(iRecognizer.view.center.x + translation.x,
iRecognizer.view.center.y + translation.y);
[iRecognizer setTranslation:CGPointMake(0, 0) inView:[viewToDrag superview]];
}
Now what I am trying here is to make this "dragView"(belongs to iDragHomeViewController) draggable in iDrag class by applying it PanGesture.
But the code is crashing.
I know some people will suggest me to use NSNotification to handle Pan action in another class but I dont want to write a single line in iDragHomeViewController and handle everything in iDrag Class only.
Is it Possible ??
Please Help.
To be sure I need to know the error output but a guess...
From UIGestureRecognizer doc:
- (id)initWithTarget:(id)target action:(SEL)action
target parameter:
An object that is the recipient of action messages sent by the receiver when it recognizes a gesture. nil is not a valid value.
Thats the reason why your app crashes. The drag object is already released while the recognizer tries to call the cellPan: method.
You initialize the iDrag object in viewDidLoad and is not retained. (It is not a member variable and not used anywhere else....). End of the viewDidLoad the iDrag object is released by ARC.
I would not make any other object responsible for handling pan gestures, unless I had a good reason for that. And would make the view controller responsible for creating gesture recognizer and handling the events.
I assume you have really good reason for that, like the handling is used by multiple views, etc... If it is the case then a better approach would be making iDrag object singleton(shared instance).
Got the answer
Just need to declare iDrag object as a property
#property(nonatomic,strong) iDrag *drag;

How can I access the name of the selector that's attached to a UIGestureRecognizer?

I have a gesture recognizer attached to a view and I'd like to be able to unit test which method it calls when the tap occurs. My gesture recognizer is created like so...
- (void)setupMyView {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myViewTapped)];
self.myView.userInteractionEnabled = YES;
[self.myView addGestureRecognizer:tap];
}
How can I access the name of the selector (myViewTapped) that is called when the tap occurs?
Thanks so much in advance for your wisdom!
Unfortunately, neither UIGestureRecognizer nor UITapGestureRecognizer exposes this information.
UIControl, for example, exposes allTargets and allControlEvents, which is basically what you are looking for, but it is unfortunately unavailable for UIGestureRecognizer
As a result, I do not believe what you want is possible without using private methods.
Use this inside the method myViewTapped,
NSLog(#"method name: %#", NSStringFromSelector(_cmd))
This one also can print the method name,
NSLog(#"%s", __PRETTY_FUNCTION__);
Source:

How do you initialize a UIGestureRecognizer?

I want to add a gesture recognizer to my button so that I can run code if the user swiped past the buttons frame. I also want this code to be different if the swipe was up, right, left, or down the button.
-(void)viewDidLoad
{
[super viewDidLoad];
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(0, 0, 100, 100);
[self.view addSubview:button];
UIGestureRecognizer *swipe=[[UIGestureRecognizer alloc]initWithTarget:button action:#selector(detectSwipe)];
[button addGestureRecognizer:swipe];
}
so, did I do the initWithTarget:action: thing correct? And now that I do this how do i Implement the detectSwipe method?
here is my idea on how to implement detectSwipe
-(IBAction)detectSwipe:(UIButton *)sender
{
/* I dont know how to put this in code but i would need something like,
if (the swipe direction is forward and the swipe is > sender.frame ){
[self ForwardSwipeMethod];
} else if //same thing for right
else if //same thing for left
else if //same thing for down
}
No, it isn't correct. The target of the gesture recognizer is not the button, it's the object on which it calls the action method when detecting a gesture (otherwise how would it know on which object call that method? In OO, a method call/message send needs an explicit method name and an instance or class).
So you would most likely want
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipe:)];
You also don't create an instance of UIGestureRecognizer directly but one if its concrete subclasses, UISwipeGestureRecognizer in this case.
After alloc-initting the recognizer, you attach it to the view you want to be recognized:
[button addGestureRecognizer:recognizer];
Then in the didSwipe: method, you can use the gesture recognizer's properties to decide what the size/distance/other property of the swipe was.
You better read some docs next time.
You probably want to be using a UISwipeGestureRecognizer. UIGestureRecognizer usually shouldnt be used unless you are subclassing it. Your code should look similar to the following.
UISwipeGestureRecognizer *swipe=[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(detectSwipe)];
swipe.direction = UISwipeGestureRecognizerDirectionRight;
[button addGestureRecognizer:swipe];
You got all right except for the target of gesture recogniser. The target is an object that receives given selector message so your initWithTarget: call should accept self as an argument unless you're implementing detectSwipe method in a subclass of your button.
H2CO3's answer is complete. Just don't forget that you're missing a colon ":" at the end of your selector! It should be like this: #selector(detectSwipe:)
The colon ":" is because your method has an argument: (UIButton *)sender

iPhone - call UISwitch that is generated in a UIView when a button is pressed

To clarify my question, my program has three lightbulb on the screen (Customized UIButton)
when any lightbulb is pressed, I programatically generate a UIView with a switch on it
when I turn on the switch, corresponding lightbulb will light up (change its background image)
However, I have trouble accessing this UISwitch since I can't declare it publicly
My code goes something like this:
#property buttonA;
#synthesize buttonA;//all three buttons have their background image set to 'off.png'
- (IBAction)lightBulbPressed:(UIButton *)sender
{
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(1,1, 64, 64)];
UISwitch *mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(0,0,64,64)];
[mySwitch addTarget:self action:#selector(onOrOff) forControlEvents:UIControlEventValueChanged];
[myView addSubview:mySwitch]
[self.view addSubview:myView];
}
So what troubles me is how to program the selector onOrOff, so that it knows which switch is being touched and change the background image of corresponding button accordingly.
Think about your method:
- (IBAction)lightBulbPressed:(UIButton *)sender {
// your method
}
You already know who called it. This piece of information is stored in sender.
So you can save it and use later in onOrOff
By the way, if you are using UISwitch you have to check
UIControlEventValueChanged
and not UIControlEventTouchUpInside.
EDIT: To pass your sender you can store its value to a NSString *buttonTapped declared in your .h file
- (IBAction)lightBulbPressed:(UIButton *)sender {
if (sender == bttOne) {
buttonTapped = #"ButtonOneTapped";
} else if (sender == bttTwo) {
buttonTapped = #"ButtonTwoTapped";
} else if (sender == bttThree) {
buttonTapped = #"ButtonThreeTapped";
}
// your method
}
- (void)onOrOff {
if ([buttonTapped isEqualToString:#"ButtonOneTapped"]) {
// Button One
} else if ([buttonTapped isEqualToString:#"ButtonTwoTapped"]) {
// Button Two
} else if ([buttonTapped isEqualToString:#"ButtonThreeTapped"]) {
// Button Three
}
}
One way to do so, is taht you give them distinct tag numbers in IB, and in - (IBAction)lightBulbPressed:(UIButton *)sender method, get their tag. e.g. NSInteger pressedButtonTag = [sender tag];, and go from there.
Also, instead of alloc/init myView every time user presses a button, you can add that view in IB, add the switch to it, put in the hierarchy of the owner but not the view, and set an outlet to it in .h. Call it whenever you need it, and again, access the switch by tag e.g. ( UISwitch *mySwitch = (UISwitch *)[myView viewWithTag:kSwitchTag]; ) and do whatever you want to do (on or off), add it to the subview and remove it later. This is more efficient.

How to dismiss keyboard when using DecimalPad

I have a UITableView with a custom cell that has a TextField. I have the DecimalPad comes up, and as we all know, there is no done key. I previously had resolved this type of issue when I had a "Decimal only" textfield on a normal UIView by handling the TouchesEnded event and then checking to see if the TextField was the first responder and if so, it would then resign, but if that technique could work now then I'm not able to figure out who's TouchesEnded I should be using (The UIView that everything is presented on, the UITableView, the Cell, the CellControler, the TextField.. I think I've tried everything).
I'm hoping there's another, cleaner way of dealing with this.
Anyone?
I think David has the best idea - here is some Monotouch code to get you started. You will need to put this in the View Controller where the decimal pad is being shown:
UIView dismiss;
public override UIView InputAccessoryView
{
get
{
if (dismiss == null)
{
dismiss = new UIView(new RectangleF(0,0,320,27));
dismiss.BackgroundColor = UIColor.FromPatternImage(new UIImage("Images/accessoryBG.png"));
UIButton dismissBtn = new UIButton(new RectangleF(255, 2, 58, 23));
dismissBtn.SetBackgroundImage(new UIImage("Images/dismissKeyboard.png"), UIControlState.Normal);
dismissBtn.TouchDown += delegate {
textField.ResignFirstResponder();
};
dismiss.AddSubview(dismissBtn);
}
return dismiss;
}
}
If you're targeting iOS 4.0 or greater you can create an inputAccessoryView containing a Done button to attach to the keyboard that will dismiss the keyboard when tapped. Here is an example from the documentation on creating a simple inputAccessoryView.
You could dismiss it when the user taps on the background; I think that's the most intuitive way.
In Interface Builder, change your View's class to UIControl. This is a subclass of UIView, so your program will work the same way, but you also get the standard touch events.
From here it's simple, create a method for the Touch Down event:
[numberField resignFirstResponder]
Of course it might be slightly different with MonoTouch -- unfortunately I don't know much about it, but wanted to help.
Hopefully you can use the concept, and modify your code accordingly.
Or you may just add some gesture to your main view.
For example:
//Just initialise the gesture you want with action that dismisses your num pad
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
UISwipeGestureRecognizer *swipeToHideNumPad = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(hideNumPad:)];
swipeToHideNumPad.delegate = self;
swipeToHideNumPad.direction = UISwipeGestureRecognizerDirectionDown;
[swipeToHideNumPad setNumberOfTouchesRequired:1];
[self.view addGestureRecognizer:swipeToHideNumPad];
}
//action
- (void)hideNumPad:(UIGestureRecognizer *)gestureRecognizer
{
[self.amountTextField resignFirstResponder];
}