How can I detect interaction anywhere outside of a UIControl, non-invasively? - ios

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.

Related

Detecting a touch anywhere on the screen

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

Passing a PanGesture to a subview

I am building a iOS app. I have a UIWebView that is added as a subview to self.view, then another view, which is mapView, added as a subview of the web view. But the mapView is send to the back of the webView. The background of the webView is transparent so that one can see the map.
see the code:
[self.webView addSubview: self.mapView];
[self.webView sendSubviewToBack: self.mapView];
Well what I am trying to do is to pass the gestures of the webView to the mapView so that the user can drag the map.
I have marked the cancelsTouchesInView property to NO for both the webView and the mapView.
I have added a gesture recognizer for the webView. The selector does get called. But what am I supposed to do next?
self.webPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action: #selector(handleWebPanGesture:)];
[self.webView addGestureRecognizer: self.webPanGesture];
I called the gestureRecognizerShouldBegin method in the webView selector, but it doesn't work.
- ( void ) handleWebPanGesture: ( UIPanGestureRecognizer *)gesture
{
NSLog(#"WebPanGesture recognizer called!");
[self.mapView gestureRecognizerShouldBegin: gesture];
[self panAction: gesture];
self.mapPanGesture = gesture; // the mapPanGesture property is the Gesture recognizer for the map
}
I also call this function
- ( IBAction )panAction:(UIPanGestureRecognizer *)sender {
NSLog(#"panAction called!");
CGPoint move = [sender translationInView:self.webView];
CGPoint newCenter = subViewCenter;
newCenter.x += move.x; newCenter.y += move.y;
self.myMapView.mapView.center = newCenter;
}
but it doesn't make the map draggable, it just moves it.
self.mapPanGesture = gesture //doesn't work as well.
How can I target the actions to the mapView so that the map gets dragged when drag on the webView?
I sure you should use overlays (MKOverlay) on mapView to show content on map. Because this is much easier way to achieve what you need.
Please read this Adding Annotations to a Map
Here I found a way around so do check out this link might be helpful.
In short, webView doesn't handle touchBegan method so u need to subclass that and in touch began method u could pass the following,
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"%#",event);
[super touchesBegan:touches withEvent:event];
[_mapView touchesBegan:touches withEvent:event];
}
or check out for below method,
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
// If the hitView is THIS view, return the view that you want to receive the touch instead:
if (hitView == self) {
return otherView;
}
// Else return the hitView (as it could be one of this view's buttons):
return hitView;
}
Above hitTest is with reference to this link.
Hope, this much info is useful to u.

Custom UIControl and Gesture Recognizer

I am trying to create a custom UIControl similar to a slider.
This control is to be the subview of a view that also has a tap gesture recognizer attached to it.
The problem now is that this tap gesture recognizer cancels the touches sent to my control. Is there a way I can override this from within the code of my control?
If I look into the standard controls in iOS it looks as if UIButton has a way of overriding the tap gesture recognizer but UISlider doesn't. So if I replace my custom control with a UIButton the tap gesture recognizer does not trigger its action, but if I replace it with a slider it does.
edit: I made a small project in Xcode to play around in. Download here https://dl.dropboxusercontent.com/u/165243/TouchConcept.zip and try to change it so that
The UICustomControl does not know about the tap gesture recognizer
The UICustomControl is not cancelled when the user taps down on the yellow box
The UICustomControl does not inherit from UIButton (that is a solution that does not feel right and might give me more headaches later on)
The code:
// inherit from UIButton will give the wanted behavior, inherit from UIView (or UIControl) gives
// touchesCancelled by the gesture recognizer
#interface UICustomControl : UIView
#end
#implementation UICustomControl
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesBegan"); }
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesMoved"); }
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesEnded"); }
-(void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesCancelled"); }
#end
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(logTap:)];
[self.view addGestureRecognizer:tapRecognizer];
UIView *interceptingView = [[UICustomControl alloc]initWithFrame:CGRectMake(10, 10, 100, 100)];
interceptingView.userInteractionEnabled = YES;
interceptingView.backgroundColor = [UIColor yellowColor];
[self.view addSubview: interceptingView];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void) logTap: (id) sender
{
NSLog(#"gesture recognizer fired");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You can configure the gesture recognizer to not cancel touches in the view it's attached using the "cancels touches in view" property:
myGestureRecognizer.cancelsTouchesInView = NO;
I'm a little bit late, but for those (like me) stumbling into this question, I used an alternative solution:
Use the delegate of the gesture recogniser.
UITapGestureRecognizer *tapGestRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissInfoBox:)];
tapGestRec.delegate = self;
[self.view addGestureRecognizer:tapGestRec];
Then do a sort of hit test in the shouldReceiveTouch delegate function when the gesture recogniser wants to handle/swallow a touch.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint location = [touch locationInView:self.view];
return !CGRectContainsPoint(self.myCustomControl.frame, location) && !CGRectContainsPoint(self.myOtherCustomControl.frame, location);
}
I did all this in my ViewController, this way the UIControl does not have to know about any sibling views and the gesture recogniser does not 'steal' taps from my custom controls and only handles 'uncaught' taps.
Also, this way you won't trigger both the gesture recogniser and the custom control, which would happen with cancelsTouchesInView.
BTW, maybe it works with UIButton because UIButton uses gesture recognisers internally? I think they understand each other, while UIControls and recognisers do not. Not sure though.
override gestureRecognizerShouldBegin(_:) in your UIControl subclass.
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isKind(of: UITapGestureRecognizer.self) {
return false
} else {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
}
You should follow this tutorial
http://www.raywenderlich.com/1768/uiview-tutorial-for-ios-how-to-make-a-custom-uiview-in-ios-5-a-5-star-rating-view
It shows you How to make a custom uiview.
And then do follow this one
http://www.raywenderlich.com/29474/ipad-for-iphone-developers-101-in-ios-6-custom-input-view-tutorial

how to make an object move by touching it in iOS

I am a newbie in Objective-C and trying to make a simple app in which when you touch the object it will move randomly for a while and stops. then you need to touch it again so it will move again and stop after a while.
I have searched for touch method and some tutorials, but the problem is I don't know how to start. It is like I need a function to move the object and one to touch it, but I don't know how to connect them and use them.
here is a tutorial which helped me a lot to get a view of functionality and it actually function in opposite way of my app. but still I can not start programming on my own.
http://xcodenoobies.blogspot.se/2010/11/under-construction.html
Any help would be appreciated, regarding how to program my logic and how to find the right methods and how to find the type of variables I need.
Step 1: Put this code in your ViewDidLoad Method, in which i have created some UIImageView and add it to View Randomly
[self.view setTag:1];
for(int i=0;i<4;i++)
{
int x = arc4random()%300;
int y = arc4random()%400;
#warning set Image here
UIImageView *imgview = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"someImage.png"]];
[imgview setFrame:CGRectMake(x, y, 25, 25)];
[imgview setUserInteractionEnabled:YES];
[self.view addSubview:imgview];
}
Step 2 : Define touchBegan Method to handle touch and move objects around the view, we have set Tag = 1 for ViewController ,because we dont want to move our mainview, only subviews will be moved
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if([[touch view] tag] != 1)
{
[UIView animateWithDuration:0.25f animations:^{
int x = arc4random()%300;
int y = arc4random()%400;
[[touch view] setCenter:CGPointMake(x, y)];
}];
}
}
What you need is to add a gesture recognizer to the view you want to be able to touch:
// In a view controller or in a UIView subclass
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self addGestureRecognizer:tapGestureRecognizer];
// Or [self.view addGestureRecognizer:tapGestureRecognizer];
- (void)handleTap:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// Animate the view, move it around for a while etc.
// For animating a view use animateWithDuration:animations:
}
}
If I get you correctly I would say that the easiest way to achieve what you request is to create a UIButton in Interface Builder and connect it to an IBAction which moves it to a random spot.. You can then add a custom graphic to the button..
Create a public method in your ViewController with return type IBAction
Create a button in IB and connect its "Touch Up Inside" outlet to your IBAction
I your IBAction method, generate a random x and y coordinate within the screens bounds and animate the movement to this point.
I will/can not go into details on the specific code since it would take way to much space.
Note that your question is very open and vague which is not considered good style on StackOverflow. Also, you might wan't to save stuff like animations until you are a bit more experienced with iOS and Objective-C
-V
Actually Apple made a demo for this.
http://developer.apple.com/library/ios/#samplecode/MoveMe/Introduction/Intro.html
You can try to modify this code to your needs. And the actual functions you where looking for where:
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
If this is the answer you where looking for please click "answered" so this question can be considered as closed :-).
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
is a method called when user touches the view. if you override this in mainview (big view) you will have to find if touched point is where the object is with some helper method as described in your link. else you can override your object's class & implement the method so you dont have to explicitly find if touched point is on the object or not.
for your requirement. i'd say override the uiimageview & inside that put the touchesbegan implementation it will just work fine.
.h file
#interface StarView : UIImageView
#end
.m file
#implementation StarView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// your code in the link with the related methods
Destination = CGPointMake(arc4random() % 320, arc4random() % 480);
// calculate steps based on speed specified and distance between the current location of the sprite and the new destination
xamt = ((Destination.x - self.center.x) / speed);
yamt = ((Destination.y - self.center.y) / speed);
// ask timer to execute moveBall repeatedly each 0.2 seconds. Execute the timer.
mainTimer = [NSTimer scheduledTimerWithTimeInterval:(0.02) target:self selector:#selector(moveBall) userInfo:nil repeats: NO];
}
dont forget to copy the moveBall method after this.
In your mainview just make an instance of StarView & add to mainview

How can I remove a UIView by the user tapping or swiping anywhere else?

I've created a mini pop-up menu for the iPhone in a UIView, and I'd like the user to be able to dismiss the view if they do anything other than select one of the options. So, if a user taps/swipes/pinches any other element on the screen, the pop-up view should disappear.
However, I don't want to detect a gesture that will stop something else from happening... For example, there is a UITableView underneath and if I swipe up or down on it, I want it to move as expected as well as dismissing the mini pop-up view.
Should I use multiple gesture recognizers, or should I use touchesBegan, or is there a better way of doing it?
Put this in your UIViewController
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch.view!=yourView && yourView) {
[yourView removeFromSuperview];
yourView=nil;
}
}
EDIT: changes made to detect touch and remove only if view exists
EDIT2: Well you could add the following to your UIButtons/UITableView methods
if (yourView) {
[yourView removeFromSuperview];
yourView=nil;
}
or add touchesBegan:withEvent: as a touchDown event to your buttons.
Both annoying to do but can't see another way to do it as the touchesBegan method doesn't get called with interactive elements.
EDIT3: Right scrap that, think i've nailed it
in your interface add the UIGestureRecognizerDelegate
#interface ViewController : UIViewController <UIGestureRecognizerDelegate> {
then in your viewDidLoad add this
UITapGestureRecognizer *tapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapMethod)];
tapped.delegate=self;
tapped.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapped];
then in your viewController add these 2 methods
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (touch.view!=yourView && yourView) {
return YES;
}
return NO;
}
-(void)tapMethod {
[yourView removeFromSuperview];
yourView=nil;
}

Resources