How to track clicks outside the custom view - ios

I've a custom view, it looks so
How can I track click on the white space (outside the view) and hide it?

U can use touchesBegan to track it like so:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch= [touches anyObject];
if ([touch view] == self.view)
{
// do stuff
}
}
For swift:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
if touch.view == self.view {
// do stuff
}
}
super.touchesBegan(touches, withEvent:event)
}

You can add a UIView under the Custom View covering the whole screen, give it an alpha of 0.1 or so. You can then add a tapGestureRecognizer to it to catch all the touches outside the custom view.
Just remember to hide the overlay too when you hide the custom view, so that the touches are not blocked afterwards.

Related

Scroll SpriteNode using touches

I want to scroll a node in sprite kit without uiscrollview
// In touchesMoved
let touch: UITouch = touches.first as! UITouch;
let location:CGPoint = touch.locationInNode(self);
let lastPosition = touch.previousLocationInNode(self)
var newLoc = selectNode.position.y + (location.y - lastPosition.y)
// Check if the top part is reached
if(newLoc < selectNodeStart){
newLoc = selectNodeStart
}
if(selectNode.position.y >= selectNodeStart){
// let sk = SKAction.moveToY(newLoc, duration: 0.1)
// selectNode.runAction(sk)
selectNode.position.y = newLoc
}
If I use the sk action the result is very horrible, but without the result is also horrible
An Elegant way of doing this in SpriteKit is using a UIPanGestureRecognizer to detect the touch, and inside of that you will create a sequence of SKActions that will accelerate or decelerate the movement. You might have to use the update method and/or touches to handle the pan stopping or another immediately starting. Unfortunately, if you want to use only SpriteKit you will have to use SKActions to implement this.
I thought I'd also mention an easier (and possible better looking) way to do this. Take a look at "ScrollKit", which works pretty well (although it does use a UIScrollView): https://github.com/bobmoff/ScrollKit
If you found that the UIScrollView doesn't pass touches up to touchesMoved, you could try a few different things.
- You can add a UITapGestureRecognizer and set on the UIScrollView CanCancelContentTouches to YES.
- You can subclass UIScrollView and override the touchesMethods so that if the user isn't scrolling, touch information will be passed up the responder chain:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent){
if (!self.dragging){
self.nextResponder.touchesBegan(touches , withEvent: event)
}
else{
super.touchesBegan(touches , withEvent: event)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent){
if (!self.dragging){
self.nextResponder.touchesMoved(touches , withEvent:event)
}
else{
super.touchesMoved(touches , withEvent: event)
}
}
override func touchesEnded(touches: NSSet, withEvent: event UIEvent){
if (!self.dragging){
self.nextResponder.touchesEnded(touches , withEvent: event)
}
else{
super.touchesEnded(touches , withEvent: event)
}
}

Override touchesBegan

I am currently developing an iOS app using swift. I used override to write my own tocuhesBegan and tochesEnded functions. Now when I use self.button.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
for touch in touches {
var tap = touch as? UITouch
var touchPoint: CGPoint = tap!.locationInView(self)
self.touchDown(touchPoint)
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
self.releaseTouch()
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
}
func touchDown(point: CGPoint){
if rippleEffect == true {
touchView.frame = touchViewInitFrame
self.addSubview(touchView)
var touchViewFrameXPosition: CGFloat = point.x - (frame.size.width) / 2
println("X position = \(touchViewFrameXPosition)")
var touchViewFrameYPosition: CGFloat = -(self.frame.size.width - self.frame.size.height) / 2
touchViewFrame = CGRectMake(touchViewFrameXPosition, touchViewFrameYPosition, frame.size.width, frame.size.width)
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseInOut, animations: {
self.touchView.frame = self.touchViewFrame
}, completion: nil)
}
}
func releaseTouch(){
if rippleEffect == true {
UIView.animateWithDuration(0.0, delay: 0.0, options: .CurveEaseInOut, animations: {
self.touchView.removeFromSuperview()
}, completion: nil)
}
}
it doesn't go to the function I specified in the selector. Is anyone else having this issue or does anyone know what's going on?
Here is the code that I used where I am having the issue. It is a subclass of UIButton.
If you override any of the touches methods, you are supposed to override all four and call super.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
//your code
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesEnded:touches withEvent:event];
//your code
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesMoved:touches withEvent:event];
//your code
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesCancelled:touches withEvent:event];
//your code
}
You probably have two different handles for your view and your button, to prove that try to touch anywhere else in the screen and your touch will be detected, however while pressing the button the touch is handle and never pass to the other handlers. You have to options, intercept all messages and handle if it should be passed or not for the original handles before/after you do what you need to do, or implement the button handler and make it pass the message up the chain:
From the apple documentation:
Overriding hit-testing ensures that the superview receives all touches
because, by setting itself as the hit-test view, the superview
intercepts and receives touches that are normally passed to the
subview first. If a superview does not override hitTest:withEvent:,
touch events are associated with the subviews where they first
occurred and are never sent to the superview.

touchesEnded not called if i do not move finger

I have a simple piece of code in which i have overridden touchesBegan,Ended,Cancelled(empty block) and touchesMoved.
If i click ( i'm testing on Desktop PC ) with the mouse touchesBegan it's called, but touchesEnded is called only when i move finger for a while. That makes impossible to recognize a single tap or a drag of the finger, and handle them differently.
I don't understand if this is an emulator problem or i am misunderstanding the whole process. Did you have the same problem?
I have a simple solution for my application, like check a "first move" variable in touchesBegan, but this is a pure technical question.
Thank you in advance.
This is all i use, apart from drawRect that it's not important.
I guess it's not a problem in my code.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.allObjects[0] as UITouch
let touchPoint = touch.locationInView(self)
println("touchesBegan")
if (Draw!){
path.moveToPoint(touchPoint)
path.addLineToPoint(touchPoint)
self.setNeedsDisplay()
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.allObjects[0] as UITouch
let touchPoint = touch.locationInView(self)
println("touchesMoved")
if (Draw!){
path.addLineToPoint(touchPoint)
self.setNeedsDisplay()
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.allObjects[0] as UITouch
let touchPoint = touch.locationInView(self)
println("touchesEnded")
if (Draw!){
path.addLineToPoint(touchPoint
path = UIBezierPath() // Create new path for next line
self.setNeedsDisplay()
}
}
I am quite sure this is an emulator problem.
Please see the code below. It works like a charm at my end.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
didMove = false;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
didMove = true;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (didMove == true)
{
//finger was moved
NSLog(#"finger was moved");
}
else
{
//it's a tap
NSLog(#"finger not moved it's a tap");
}
}
While we are at it, can I bring your attention to UIGestureRecognizers? Try to use them since they make life a breeze.

Using isKindOfClass with Swift

I'm trying to pick up a bit of Swift lang and I'm wondering how to convert the following Objective-C into Swift:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
if ([touch.view isKindOfClass: UIPickerView.class]) {
//your touch was in a uipickerview ... do whatever you have to do
}
}
More specifically I need to know how to use isKindOfClass in the new syntax.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
???
if ??? {
// your touch was in a uipickerview ...
}
}
The proper Swift operator is is:
if touch.view is UIPickerView {
// touch.view is of type UIPickerView
}
Of course, if you also need to assign the view to a new constant, then the if let ... as? ... syntax is your boy, as Kevin mentioned. But if you don't need the value and only need to check the type, then you should use the is operator.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let touch : UITouch = touches.anyObject() as UITouch
if touch.view.isKindOfClass(UIPickerView)
{
}
}
Edit
As pointed out in #Kevin's answer, the correct way would be to use optional type cast operator as?. You can read more about it on the section Optional Chaining sub section Downcasting.
Edit 2
As pointed on the other answer by user #KPM, using the is operator is the right way to do it.
You can combine the check and cast into one statement:
let touch = object.anyObject() as UITouch
if let picker = touch.view as? UIPickerView {
...
}
Then you can use picker within the if block.
I would use:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let touch : UITouch = touches.anyObject() as UITouch
if let touchView = touch.view as? UIPickerView
{
}
}
Another approach using the new Swift 2 syntax is to use guard and nest it all in one conditional.
guard let touch = object.AnyObject() as? UITouch, let picker = touch.view as? UIPickerView else {
return //Do Nothing
}
//Do something with picker

IOS: correct number of touch on screen

In my app I need to catch the exact number of finger on the screen, I try two ways but I have 2 different problem.
First way:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSArray *allTouches = [touches allObjects];
int count = [allTouches count];
NSLog(#"number of touch:%d", count);
}
this give me a NOT accurate number of touches if I use more finger at the same time.
Second way:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
int num = [touches count];
totalTouch = totalTouch+num;
NSLog(#"number of touch:%d", totalTouch);
}
in this way I use a global var (totalTouch) that I increment everytime touchbegan is called and I have a perfect number of touches.
Naturally i set at '0' this var in the touchend
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
totalTouch = 0;
}
My problem is that, with the second way I do a control in touchbegan, this:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
int num = [touches count];
totalTouch = totalTouch+num;
if (totalTouch == numberToVerify){
//IT ENTER HERE EVERYTIME INSIDE THIS IF
}
else{
}
}
so everytime it enter inside if-conditions, and I don't want it, I want do this control only when I have a final number of touch...
Within your
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
int num = [touches count];
totalTouch = totalTouch+num;
NSLog(#"number of touch:%d", totalTouch);
}
You can get the number of fingers on the screen with
[[event allTouches]count]
This can be accessed from - (void)touchesBegan: - (void)touchesEnded: or - (void)touchesMoved:
In touchesBegan:withEvent:, the touches argument only contains touches in the “begin” phase (UITouchPhaseBegan). In touchesEnded:withEvent:, the touches argument only contains touches in the “end” phase (UITouchPhaseEnded). Similarly for touchesMoved:withEvent: and touchesCancelled:withEvent:.
If you want all touches known to the system, look at event.allTouches.
If you want all touches known to the system that belong to a specific view, look at [event touchesForView:someView].
UIEvent Class Reference
UPDATE For Swift 3 :
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
var countTouch = event?.allTouches?.count
//Do whatever you want with that
}
If you want to know if it changed at any moment, do the same in touchesMoved and put it in an array, you'll be able to analyze it.
Like this :
var countTouch:[Int] = []
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
countTouch.append((event?.allTouches?.count)!) //Not really necessary
//Do whatever you want with that
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
countTouch.append((event?.allTouches?.count)!)
}
Hope it helped someone, don't hesitate to edit or improve it. I'm a beginner in Swift, so it's totally possible there are mistakes.
The issue with your 2nd solution is that in your touchesEnded function, you are setting totalTouch to 0 instead of decrementing by the size of allObjects. The function touchesEnded does not get called when all touches are ended; it is called when any touches are ended.
If I touch once, touchesBegan is called. If I touch again simultaneously, touchesBegan is called again and totalTouch is now at 2. If I lift one of the touches, touchesEnded is called with an allObjects length of 1, because only one touch was lifted. If we had set the counter to 0 here instead of decrementing the counter by the size of allObjects, this would mess up the whole process. This is my solution:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
...
touchCount += touches.allObjects.count
...
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
...
touchCount -= touches.allObjects.count
...
}

Resources