How to identify which event is passed to parent via nextResponder? - ios

I have UITableViewCells each with a few UIImageViews. I want to pass the event to the parent UITableViewController so it can act upon it. How can I send information back to the UITableViewController to let it know which UIImageView triggered the event. Using the below code, it seems the UITableViewController touchesEnded is triggered when any of the children UITableViewCells fire an event. Any way to pass information in the UIEvent?
Is there a better way to go about event handling?
//UITableViewCell
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view] == imageView)
{
[[self nextResponder] touchesEnded:touches withEvent:event];
}
}
//UITableViewController
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
NSLog(#"clicked");
}

For a quick and dirty way, you can use the objc_setAssociatedObject and objc_getAssociatedObject of ObjC runtime.
#import <objc/runtime.h>
set the object:
// static char key;
objc_setAssociatedObject(event, &key, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
and get the object:
id yourInterestObj = objc_getAssociatedObject(event, &key);
But I don't recommend this way, it may breaks the MVC. I think your should use the Responder Chain patter to handle this situation, check the documentation for -sendAction:to:from:forEvent: of UIApplication.

Related

add gesturerecognizer on top right side of navigation bar

I use the following code to add a gesture recognizer on the top right side of uinavigationbar but i get result if i tap anywhere on the navbar. How am i supposed to make a gesture for the top right corner?
- (void)handleGestureForTopRightBarButtonItem:(UIGestureRecognizer *)gestureRecognizer {
CGPoint p = [gestureRecognizer locationInView:self.navigationController.navigationBar];
if (CGRectContainsPoint(CGRectMake(self.navigationController.navigationController.view.width-20,0,100,self.navigationController.navigationController.view.height), p)) {
NSLog(#"got a tap on the right side in the region i care about");
} else {
NSLog(#"got a tap on the right side, but not where i need it");
}
}
As UIGestureRecognizer is reporting to a class object there are a couple of ways to solve this. UIGestureRecognizer was not meant to be stacked multiple times on the same view, if you do so you would very likely drain more Energy than you need apart from the loss of CPU power and lots of comparison code that has to distinguish all the running recognisers. But it would work..
a) write code that compares its coordinates and expected values and if they match in the range you want do your actions.
b) create another object that is living only in the coordinates you want and has it own UIGestureRecognizer. Not ideal, as written above.
c) use the power of UIControl which are also inherited UIView's that are also UIResponders.
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event; // touch is sometimes nil if cancelTracking calls through to this.
- (void)cancelTrackingWithEvent:(nullable UIEvent *)event;
d) use the power of UIView without a UIGestureRecognizer. Which by the way basically works also on CALayers, they do not have the sendAction methods, they are not UIControls.
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL inside = [super pointInside:point withEvent:event];
if (inside && event.type == UIEventTypeTouches) {
//[super sendActionsForControlEvents:UIControlEventTouchDown];
}
return inside;
}
e) code some Class that inherits from UIResponder, which basically is what UIControls do and use their API instead so you make use of touch coordinates as well.
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
[self someCoRoutineWithTouch:touch];
}
//[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved :(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
//do some stuff per touch
}
//[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
//do some stuff per touch
}
//[super touchesEnded:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
Just don't forget that UIGestureRecognizer is basically for gestures that are made of touches, even multiple touches but not mainly to catch touches in general, despite in a lot of examples they are used instead of coding a proper UIControl. Also dont forget in a Devices edges the recognition of gestures is limited by the nature of finger size.

Prevent parent view controller from receiving UITouch in child controller

So, in my app, then a map annotation is pressed, It reveals a modal view controller. Then when an element that modal controller is pressed, I open another, smaller controller. In both controllers, I have implemented the method
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
if (!CGRectContainsPoint([self.view viewWithTag:21].frame, touchPoint)) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
Here is an image that shows my layout:
My problem is that when I touch in yellow circle one, the second modal dismisses, and after that, the first modal dismisses. SO, how can I prevent the first modal from receiving the touch.
PS: both modals are transparent UIViews, with smaller views inside them to display the content.
Obviously the event is passed through the view controller to the next responder.
In the class reference of UIResponder.
The default implementation of this method does nothing. However
immediate UIKit subclasses of UIResponder, particularly UIView,
forward the message up the responder chain. To forward the message to
the next responder, send the message to super (the superclass
implementation); do not send the message directly to the next
responder. For example,
[super touchesMoved:touches withEvent:event];
If you override this method without calling super (a common use
pattern), you must also override the other methods for handling touch
events, if only as stub (empty) implementations.
So if you don't want the event to forward to the next responder, you should override the other methods.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
if (!CGRectContainsPoint([self.view viewWithTag:21].frame, touchPoint)) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}

iOS: Keyboard appear automatically in ios8 but not able to resign textfield

Keyboard appear automatically after received web service result in iOS8.3 while using xcode6.3.1 / 6.3.2. But it work correctly in previous versions.
Am using following code to resign keyboard when touch inside of view
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint ForgotTxt_call = [touch locationInView:userFrgtPwdTxtFld];
BOOL isForgotTxt = [userFrgtPwdTxtFld pointInside:ForgotTxt_call withEvent:nil];
if (!isForgotTxt)
{
[self.vew endEditing: YES];
}
}

How to send message to UIViewController

I have a problem as following, I've developed a ios app, to be concice , It has a UIViewController as parent, also it has a button , and the UIViewController popup a transparent UIView as a mask. When I click on the UIView(exactly within the underlying button boundary ) , the button could not receive any event(such as "touch up inside"), how could the button get the "touch up inside" event from transparent the UIView which is above the UIViewController?
This is not possible directly as the event triggered will not be for the button as button is not visible
(ie. another View is completly covering the button and is blocking the interaction with the user).
But i can give u a work around.
1.Declare a Bool Variable in your UIViewController
2.Implement the touches methods as shown below
- (void)touchesBegin:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch=[touches anyObject];
CGPoint p=[touch locationInView:self.view];
if(CGRectContainsPoint(button.frame, p) && !boolVariable) {
boolVariable = YES;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch=[touches anyObject];
CGPoint p=[touch locationInView:self.view];
// If the below condition is true then it mean there the user tapped on the same location as that of button..(touchesEnded and touchesCanceled was not called) so the event is just like touchUpInside
if(CGRectContainsPoint(button.frame, p) && boolVariable) {
boolVariable = NO;
[Here you can call the method which you wanted to call on touchUpInside of the button];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
boolVariable = NO;
}
- (void)touchesCanceled:(NSSet *)touches withEvent:(UIEvent *)event {
boolVariable = NO;
}
I was not able to test the able code on Xcode but i think it will work..
Note: The frame of the button should be with respect to the UIViewController.
Hope this helps u out :)
UIView instances, do not listen for touches. In order to get callbacks for events such as "touch up inside" are only sent to subclasses of UIControl
The most basic concrete subclass is UIButton.
Without code or more details about your app's setup, it's difficult to give better advice.

Touch Events for UITableview

I have a UIView overlaying a subclassed UITableview. The problem is that ,I cant get the tableview to scroll. I have tried overriding touchesBegan,touchesMoved,touchesEnded. I then tried to override hittest but that seemed to have no affect.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"SMTable.touches began %#",NSStringFromCGPoint(touchPoint));
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"SMTable.touches moved %# for :%p",NSStringFromCGPoint(touchPoint),touch.view);
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"SMTable.touches ended %#",NSStringFromCGPoint(touchPoint));
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
[super touchesCancelled:touches withEvent:event];
}
- (UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
//NSLog(#"SMTable.hitTest %#",NSStringFromCGPoint(point));
return [super hitTest:point withEvent:event];
}
If your UIView is above your UITableView, then all touch events will land in that UIView and your UITableView will not scroll. You need to disable interaction for your top most `UIView˜
When you need to create a specialized UITableView you are almost always better off using a UIViewController that contains a UITableView rather than mucking around in the UITableView hierarchy if at all possible. Apple are doing quite a bit of stuff in the tableview hierarchy which makes adding your own custom views to it often go awry. So, the short answer is : avoid inserting your own views into the tableView hierarchy.
In fact, I almost never use a UITableViewController subclass anymore. I always find myself needing to customize the view controller in a way that isn't easily supported from a UITableViewController-- such as creating a view to overlay the tableView as you are doing. Instead, create your controller like this:
#interface MyViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic,strong) IBOutlet UITableView *tableView
#end
If you are using Interface Builder, drop your tableView into the view controller's view and set the delegate and datasource to the view's owner. Or you can do the same thing in code via the viewDidLoad method. In either case, at this point you can treat the view controller exactly as if it were a UITableViewController with the added benefit of being able to do things like inserting views into self.view without things going horribly awry.

Resources