I have problems detecting touch events on my UIImageView. I have sub classed UIImageView and overrided canBecomeFirstResponder, touchedBegan and touchesEnded:
#implementation ImageViewNext
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.userInteractionEnabled = YES;
}
return self;
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches Began event");
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches Ended");
}
I have ensured that all the parent views have User Interaction Enabled.
But I don't get my events?
I have tried to find the solution here but without any luck. But in my search I found that some people use "UIGestureRecognizer" to get the same effect. So as a side question, what is the "right" solution use the override method or use UIGestureRecognizer? I don't like the solution where a normal button is given an image, so please keep those post away :)
Are you sure your initWithFrame: is called? UIViews have two designated initializers, initWithFrame: and initWithCoder, depending on how they are created.
I don't know why you're not getting the touch events, but to answer the other part of your question using a UIGestureRecognizer will significantly simplify your touch handling for any of the gestures for which there's a recognizer available. It's much simpler to use and you'll need less code.
Furthermore, if in the future you need to negotiate between different touch events (is it a tap of a drag type questions) then all that logic is handled trivially.
Related
I am wanting to know when a user has touched anywhere on the screen of my app.
I have looked into using -(UIResponder *)nextResponder but unfortunately this will not work, as I am also reloaded a table automatically, so this gets trigged when that occurs.
I have also tried a gesture recognizer, with the following code. But this will only recognise touches on the view. Where as I have many buttons the user will be using to operate the app. I would like to avoid adding a gesture recogniser or code for this in every button and segment control I have on the screen
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapOnView:)];
[self.mainView addGestureRecognizer:tap];
- (void)tapOnView:(UITapGestureRecognizer *)sender
{
//do something
}
I have also tried -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event , but this has the same issue as the gesture recognizer.
I was wondering if there is any way I could achieve this task. I was hoping that I may be able to recognise the type of event from within the nextResponder, and then I could detect if it is button for example.
EDIT: The reason I am working on this is that my app needs to stay active and the screen cannot be locked (so I have disabled screen locking). To avoid excessive use of power, I need to dim the screen, but then return the brightness back to the original level once the app is touched. I need this feature to only occur on 1 of my viewcontrollers.
As mentioned by Ian MacDonald, using hitTest:: is a great solution to detect user interaction on an app wide scale, including when buttons, textfields, etc, are selected.
My solution was to subclass UIWindow and implement the hitTest method.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// do your stuff here
// return nil if you want to prevent interaction with UI elements
return [super hitTest:point withEvent:event];
}
You could attach your UITapGestureRecognizer to your [[UIApplication sharedApplication] keyWindow].
Alternatively, you could override hitTest: of your root UIView.
Is there a particular task you are hoping to accomplish? There may be a better way than assigning an "anywhere" gesture.
Edit: Use hitTest:.
#interface PassthroughView : UIView
#property (readonly) id target;
#property (readonly) SEL selector;
#end
#implementation PassthroughView
- (void)setTarget:(id)target selector:(SEL)selector {
_target = target;
_selector = selector;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
[_target performSelector:_selector];
return nil;
}
#end
#implementation YourUIViewController {
PassthroughView *anytouchView;
}
- (void)viewDidLoad {
// Add this at the end so it's above all other views.
anytouchView = [[PassthroughView alloc] initWithFrame:self.view.bounds];
[anytouchView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[anytouchView setTarget:self selector:#selector(undim)];
[anytouchView setHidden:YES];
[self.view addSubview:anytouchView];
}
- (void)undim {
[anytouchView setHidden:YES];
}
- (void)dim {
[anytouchView setHidden:NO];
}
#end
Your edit adds more clarity to your question.
The reason I am working on this is that my app needs to stay active
and the screen cannot be locked (so I have disabled screen locking).
To avoid excessive use of power, I need to dim the screen, but then
return the brightness back to the original level once the app is
touched.
Since you are controlling the screen brightness, you can add one transparent view controller before dimming screen on top of your root controller which does only one job, listen to tap using Tap gesture. And on tap you can dismiss the view controller and adjust brightness to previous state.
By doing so you dont have to worry about buttons being clicked as they will be below the transparent view controller. Since its a whole new view controller sitting on top of stack you dont have to modify your existing code as well.
Ok I have had a similar problem before.
As I remember I subclassed the UIWindow for full screen detection and made it First responder.
Than I overridden the touch to handle from subclasses.
You can also use code to identify the control that is been touched.
#import <QuartzCore/QuartzCore.h>
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setMultipleTouchEnabled:YES];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Enumerate over all the touches
[touches enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
// Get a single touch and it's location
UITouch *touch = obj;
CGPoint touchPoint = [touch locationInView:self.view];
...
}];
}
To disable the locking of screen I used below code:
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
I used following functions to dim or increase the screen brightness
[[UIScreen mainScreen] setBrightness:0.0f]; //and
[[UIScreen mainScreen] setBrightness:1.0f];
I've googled on how to dismiss keyboard when touching a blank area in UITableView in iOS, and there're several ways to solve this. Like using delegate, UITapGestureRecognizer, - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event and - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event.
I decide to take hitTest by subclassing the corresponding UIView class and override this method like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *result = [super hitTest:point withEvent:event];
[self endEditing:YES];
return result;
}
This really works, will dismiss the virtual keyboard when I touch / scroll / swipe / pinch ... somewhere else, but another problem shows up.
The keyboard is active or shown when I touch one UITextField object, then I touch the same UITextField object, here is the problem, the keyboard try to dismiss but not completely, in the middle of somewhere it begin to show up, doing this kind of weird animation. Most of the case in our APPs, the keyboard should stay still when we touch the same UITextField object. Is there a nice and simple way to solve this problem?
Solved:
Finally, I figure it out myself. Thanks to #Wain, thanks to #Wain's hint. I do check the result before I invoke [self endEditing:YES];. Here is the modified code:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *result = [super hitTest:point withEvent:event];
// You can change the following condition to meet your own needs
if (![result isMemberOfClass:[UITextField class]] && ![result isMemberOfClass:[UITextView class]]) {
[self endEditing:YES];
}
return result;
}
As per #iBCode answer, if I select same text field multiple times then keyboard automatically hide and show immediately, to avoid that I have added one more condition and added code below in swift languagge
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let resultView = super.hitTest(point, with: event) {
if resultView.isMember(of: UITextField.self) ||
resultView.isKind(of: UITextField.self) ||
resultView.isMember(of: UITextView.self) ||
resultView.isKind(of: UITextView.self) {
return resultView
}
if !resultView.isMember(of: UITextField.self) && !resultView.isMember(of: UITextView.self) {
endEditing(true)
}
return resultView
}
return nil
}
Check the class of the result so that you can limit when you end editing. If the hit target was an editable class (like a text field, or a switch) then don't end the editing session.
I have a custom UIControl which, when tapped, goes into a confirm state and, when tapped again, performs the desired action.
I want to have this control go back into its initial state if the user interacts anywhere else on the screen. Is there a non-invasive way to achieve this?
Clarification: I consider code invasive if I can't contain it within this control. I'd like to give the dev of another app this code, which they could use to add the control to their app, without having to mess around with code anywhere else in the app. If this isn't possible, fine, but the question is how to accomplish this non-invasively.
You can think of a UITapGestureRecognizer placed on top of everything that triggers the dismiss only when the touch happens outside of you UIControl bounds.
Something like
- (void)presentConfirm {
// whatever
self.dismissRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismiss:)];
self.dismissRecognizer = self;
[self.view addGestureRecognizer:self.dismissRecognizer];
}
- (void)dismiss:(UIGestureRecognizer *)gestureRecognizer {
// do stuff
[self.view removeGestureRecognizer:self.dismissRecognizer];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint touchPoint = [touch locationInView:self.view];
return !CGRectContainsPoint(self.control.frame, touch));
}
Basically you're triggering the dismiss method only when the touch happens outside the UIControl frame (I assumed your control is referenced as self.control).
Also you're going to need a dismissRecognizer property declared as
#property (nonatomic, strong) UITapGestureRecognizer *dismissRecognizer;
and to prevent warnings you should also declare that your controller conforms to the UIGestureRecognizerDelegate protocol.
You could try to accomplish this with the touchesBegan method:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
if(myUIControl.isInConfirmState){ // check if your control is in confirmed state
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if(!CGRectContainsPoint(myUIControl.frame, touchLocation)){
// set myUIControl to it's initial state
}
}
}
Hope this helps.
This question already has answers here:
UIScrollView touchesBegan
(8 answers)
Closed 9 years ago.
I have subclassed UIScrollView and implemented
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method in it.
But Nothing happens when click or hold the scroll view!
Edit: I also need to use touchesEnded method
I think UIScrollView has two gesture recognizer. They are responsible for handling touch sequences, so they swallow the touch events.
Use scrollView delegate methods to handle drag gestures or the
touchesShouldBegin:withEvent:inContentView:
method to handle scrollview content touches and
touchesShouldCancelInContentView:
to cancel it.
As alternative you can manipulate the panGesture recognizer of the scrollView to pass the event to the next responser.
In your subclass of UIScrollView override the hitTest method like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = nil;
for (UIView *child in self.subviews)
if ([child pointInside:point withEvent:event])
if ((result = [child hitTest:point withEvent:event]) != nil)
break;
return result;
}
I think you are using scrollview as a subview.So in that case you can use gesture coz same problem i've face it.
You can do using UITapGestureRecognizer like this ...
-(void)viewDidLoad
{
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gr.delegate = self;
[self.scrollView addGestureRecognizer:gr];
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
// do here whatever you wanna ..........
}
Solved!
Used LongPressedGestureRecognizer. The action method keeps getting called till the user holds the view. From that i can figure out the state of the gestureRecognizer (UIGestureRecognizerStateBegan, ...Ended, ...Cancelled)
I want to respond to touch events (specifically UIControlEventTouchUpInside and UIControlEventTouchUpOutside) on a UITextField, but the only event that comes through is UIControlEventTouchCancel.
I can't use UIControlEventEditingDidBegin, because it only occurs when field gains focus, which is not what I'm trying to detect. I want to know when the user lifts their finger, and whether it was inside or outside the text field, regardless of current focus state.
Does anyone know a way to accomplish this?
To be clear I've tried using addTarget:action:forControlEvents and it does not work. The touch up event is prevented by the cancel. I want a way to detect the touch up before the UITextField consumes it and produces the Cancel event.
Also, I'd like to avoid creating any extra views if possible.
Well a round about way of doing it would be to create a UIButton with the fill set to clear to cover all of the space, and a UITextField on top of it. If the user touches in the text field, the text field should send an event, and if the user touches outside of the text field, the UIButton will send the event.
Good answer here:
Change default touch events for UITextField
Use the text field's delegate and do what you want to do in textFieldShouldBeginEditing:
You can return NO to prevent the default behavior.
You can create category for the UiView and override the touchesBegan meathod as follows.
It is working fine for me.And it is centralize solution for this problem.
#import "UIView+Keyboard.h"
#implementation UIView(Keyboard)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.window endEditing:true];
[super touchesBegan:touches withEvent:event];
}
#end
I have faced this problem just today. My goal was to change textfield's background color on touch down and revert it back on touch up/cancel.
It is weird that touch down is sent to target from UITextField, but touch up inside, touch up outside and even touch cancel are not.
I found the solution. It is simple. Just create a subclass of UITextField and override touchesEnded: and touchesCancelled:.
#interface TextField : UITextField
#end
#implementation CuteTextField
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesEnded:touches withEvent:event];
self.backgroundColor=GetDefaultBackgroundColor();
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesCancelled:touches withEvent:event];
self.backgroundColor=GetDefaultBackgroundColor();
}
#end
Add a target for your desired event programatically, or, use IB.
UITextField * touchyTextField = [[UITextField alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 21.0)];
[touchyTextField addTarget:self
action:#selector(yourDesiredMethod)
forControlEvents:UIControlEventTouchUpInside];