Custom UISlider: avoid updating when dragging outside - iphone

I'm quite new to iPhone development and I'm building my first app :)
In one of my view controllers I built a customSlider that should behave as the native "slide to unlock" slider.
My doubt right now is how to implement the "drag outside" behaviour. As said, I would like to have it exactly as the native slider, that means that when the finger is dragging the slider if it moves out the slider, the slider should be going to zero.
My doubt is not on the animation part (I'm already using animation block successfully), but on control events part. Which control event should I use?
I'm using:
[customSlider addTarget:self action:#selector(sliderMoved:) forControlEvents:UIControlEventValueChanged];
to handle the sliding part (finger sliding the cursor), and
[customSlider addTarget:self action:#selector(sliderAction:) forControlEvents:UIControlEventTouchUpInside];
to handle the release part, but the problem is that if I release the finger outside, the sliderAction function it's not called.
EDIT:
I've tried to implement the solution #Bruno Domingues gave me, but I'm realizing that the issue is that by default UISlider keep getting updated even if the finger is dragged outside of it (try to open for example the Brightness section in System Preferences and you'll see that the slider will keep on updating even if you drag outside of it). So my question could be redefined: How to avoid this default behaviour and have the slider updating only when the finger is moving on it?

Simply interrupt the touches methods in your custom subclass and only forward the touches you want acted on to the superclass, like so:
in .h:
#interface CustomSlider : UISlider
#end
in .m:
#import "CustomSlider.h"
#implementation CustomSlider
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesEnded:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesCancelled:touches withEvent:event];
}
#end
Please note that this implementation will start updating the control if your finger moves back to the control. To eliminate this, simply set a flag if a touch is received outside of the view then check that flag in subsequent touches methods.

You need to set a [slider addTarget:self action:#selector(eventDragOutside:) forControlEvents:UIControlEventTouchDragOutside];
And add a function
- (IBAction)eventDragOutside:(UISlider *)sender {
sender.value = 0.0f;
}

Related

Defining maximum variance of an UITextView

If you have a normal UITextView with loads of text in it you can swipe up and down:
if you swipe left or right in a very horizontal line, nothing will happen
if you swipe left or right with a very slight angle, you will swipe up or down
So there is obviously some kind of class definition which tells the UITextView when to react to a swipe and when not to. Something like a maximumVariance constant, I guess.
My problem is that I'm trying to subclass an UITextView to enable left/right swipe. However, you will need to swipe in a pretty straight line, otherwise, you'll get an up or down swipe. So my question is if there is someway to re-define the standard variance variable of an UITextView?
Here is my subclass .m file. Note that the kMaximumVariance doesn't really have any effect as this is overwritten by whatever variance the standard UITextView class provides:
//
// SwipeTextView.m
// Swipes
//
#import "SwipeTextView.h"
#import "PageView.h"
#define kMinimumGestureLength 15
#define kMaximumVariance 100
#implementation SwipeTextView
#synthesize delegate, gestureStartPoint;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
UITouch *touch =[touches anyObject];
gestureStartPoint = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self resignFirstResponder]; // this hides keyboard if horizonal swipe
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:self];
CGFloat deltaXX = (gestureStartPoint.x - currentPosition.x); // positive = left, negative = right
CGFloat deltaYY = (gestureStartPoint.y - currentPosition.y); // positive = up, negative = down
CGFloat deltaX = fabsf(gestureStartPoint.x - currentPosition.x); // will always be positive
CGFloat deltaY = fabsf(gestureStartPoint.y - currentPosition.y); // will always be positive
NSLog(#"deltaXX (%.1f) deltaYY (%.1f) deltaX (%.1f) deltaY (%.1f)",deltaXX, deltaYY, deltaX, deltaY);
if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
if (deltaXX > 0) {
NSLog(#"Horizontal Left swipe - FINISHED detected");
}
else {
NSLog(#"Horizontal Right swipe - FINISHED detected");
}
}
}
#end
EDIT:
Suggestion to cancel touches reaching UITextView (see below):
// Prevent recognizing touches
- (BOOL)gestureRecognizer:(UISwipeGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionLeft) {
// Left swipe: I want to handle this
return YES;
}
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionRight) {
// Right swipe: I want to handle this
return YES;
}
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionUp) {
// Up swipe: textView needs to handle this
return NO;
}
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionDown) {
// Down swipe: textView needs to handle this
return NO;
} else
return YES;
}
-(void)viewDidLoad
{
UITapGestureRecognizer *GesRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doSomething:)];
[GesRec setCancelsTouchesInView:NO];
[self addGestureRecognizer:GesRec];
[GesRec release];
}
-(void)doSomething
{
NSLog(#"doSomething");
}
Try adding an UISwipeGestureRecognizer to the view and set cancelsTouchesInView to false so the swipe gesture is not passed through. This should let you detect left and right motion more accurately.
If this doesn't do the trick, implement your own subclass of UIGestureRecognizer and add that instead.
Edit
You add the gesture recognizer to the UITextView in your controller's viewDidLoad method.
In your delegate methods, you can't return NO in gestureRecognizer:shouldReceiveTouch: because it means the gesture recognizer won't examine the incoming touch object. In your case you want to return YES when the swipe is horizontal and NO otherwise, so vertical swipes are passed through while horizontal swipe is completed handled by you.
No subclass needed. set textView.userInteractionEnabled to NO and than put a transparent UIView over your textView. handle touches and gestures anyway you want on that UIView and make the textView respond to these programatically.

drag UIView like apps in the home screen iPhone

I have a view control and inside I plan to place some controls like buttons textbox etc... I can drag my view along the x axis like:
1)
2)
with the following code:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if( [touch view] == ViewMain)
{
CGPoint location = [touch locationInView:self.view];
displaceX = location.x - ViewMain.center.x;
displaceY = ViewMain.center.y;
startPosX = location.x - displaceX;
}
CurrentTime = [[NSDate date] timeIntervalSince1970];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
if( [touch view] == ViewMain)
{
CGPoint location = [touch locationInView:self.view];
location.x =location.x - displaceX;
location.y = displaceY;
ViewMain.center = location;
}
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
double time = [[NSDate date] timeIntervalSince1970]-CurrentTime;
UITouch *touch = [[event allTouches] anyObject];
if( [touch view] == ViewMain)
{
CGPoint location = [touch locationInView:self.view];
location.x =location.x - displaceX;
location.y = displaceY;
ViewMain.center = location;
double speed = (ViewMain.center.x-startPosX)/(time*2);
NSLog(#"speed: %f", speed);
}
}
not that I have to add the global variables:
float displaceX = 0;
float displaceY = 0;
float startPosX = 0;
float startPosY = 0;
double CurrentTime;
the reason why I created those variables is so that when I start dragging the view the view moves from the point where I touch it instead of from the middle.
Anyways if I touch a button or image the view will not drag even though the images have transparency on the background. I want to be able to still be able to drag the view regardless if there is an image on top of the view. I where thinking that maybe I need to place a large transparent view on top of everything but I need to have buttons, images etc. I want to be able to drag a view just like you can with:
note that I was able to drag the view regardless of wither I first touched an app/image or text. How could I do that?
I think your problem is that if you touch a UIButton or a UIImageView with interaction enabled, it doesn't pass the touch along.
For the images, uncheck the User Interaction Enabledproperty in IB.
For the buttons that are causing touchesBegan:withEvent:, etc. to not get called, then look at the following link: Is there a way to pass touches through on the iPhone?.
You may want to consider a different approach to this problem. Rather than trying to manually manage the content scrolling yourself you would probably be better off using a UIScrollView with the pagingEnabled property set to YES. This is the method Apple recommends (and it's probably the method used by Springboard.app in your last screenshot). If you are a member of the iOS developer program check out the WWDC 2010 session on UIScrollView for an example of this. I think they may have also posted sample code on developer.apple.com.

How can we check that which image in imageview is being called

I am having 2 imageviews with image in it. I want that when I click on the first image the image should get selected and if it is selected it should return me value TRUE or 1 that should be saved in sqlite database. How is this possible?
By using UITouch class methods you will get which image view is touched, or you can put imageview inside button then you will get click event.
You go with Touch events.Capture the touch points and perform your actions.Here I will give u a sample structure to do this,
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location= [touch locationInView:self.view];
if(CGRectContainsPoint(firstImage.frame, location)) {
//set some flag like
selectionFlag=1; }
else if(CGRectContainsPoint(secImage.frame, location)){
selectionFlag=2; }
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
if(CGRectContainsPoint(firstImage.frame, location)) {
if(selectionflag==1) {
//do ur db actions }
}
else if(CGRectContainsPoint(secImage.frame, location)) {
if(selectionflag==2) {
//do ur db actions }
}
selectionflag=0;
}
first do
[self.*yourimageViewname* setUserEnteractionEnabled:YES];
BOOL select1,secelect2;
select1=NO;
select2=NO;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch=[touches anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
//give the beginning and ending x and y points in condition to check which imageView is taped
if(touchLocation.x>1 && touchLocation.x<116 && touchLocation.y>133 && touchLocation.y<233)
{
select1=YES;
}
else if(touchLocation.x>120 && touchLocation.x<300 && touchLocation.y>133 && touchLocation.y<233)
{
select2=YES;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
On the condition which boolean variable is true you can save or whatever you want to do further coding your application.
Using the touches is one way as explained in the answers below. If you have just two imageview you can also try to have two custom transparent buttons on top of that imageview and therefore you can easily know which image is touched based on the tag you give for the buttons.
What you could also do is create two buttons (instead of UIImageViews) that have images. They should display roughly the same (you could even disable the touch states, etc). And you get UIResponder events for free (as in; you can target the action to a selector).
UPDATE: here's roughly how (didn't bother configuring the buttons though).
UIButton *button1 = [UIButton buttonWithType:UIButtonTypeCustom];
[button1 setImage:yourImage forState:UIControlStateNormal];
[button1 addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
// .. now create a second button .. //
And your button will go to the following method when touched:
- (void)buttonTouched:(id)sender
{
// .. add your stuff to your database .. //
// .. you can identify your button by sender, or give the button a tag (number) to identify it .. /
}

iPhone process a swipe by user

Simple question here: How can I detect when I user swipes their finger on the screen of the iPhone?
You need to implement a gesture recognizer in your application.
In your interface:
#define kMinimumGestureLength 30
#define kMaximumVariance 5
#import <UIKit/UIKit.h>
#interface *yourView* : UIViewController {
CGPoint gestureStartPoint;
}
#end
kMinimumGestureLength is the minimum distance the finger as to travel before it counts as a swipe. kMaximumVariance is the maximum distance, in pixels, that the finger can end above the beginning point on the y-axis.
Now open your interface .xib file and select your view in IB, and make sure Multiple Touch is enabled in View Attributes.
In your implementation, implement these methods.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
gestureStartPoint = [touch locationInView:self.view];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:self.view];
CGFloat deltaX = fabsf(gestureStartPoint.x - currentPosition.x);
CGFloat deltaY = fabsf(gestureStartPoint.y - currentPosition.y);
if(deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance){
//do something
}
else if(deltaY >= kMinimumGestureLength && deltaX <= kMaximumVariance){
//do something
}
}
This is one way to implement a swipe recognizer. Also, you really should check out the Docs on this topic:
UISwipeGestureRecognizer
The UIGestureRecognizer is what you want. Especially the UISwipeGestureRecognizer subclass
Ah, I was able to answer my own question: http://developer.apple.com/library/ios/#samplecode/SimpleGestureRecognizers/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009460
Thanks for the help everyone!

UIScrollView touches vs subview touches

Please can someone help sort a noob out? I've posted this problem on various forums and received no answers, while many searches for other answers have turned up stackOverflow, so I'm hoping this is the place.
I've got a BeachView.h (subclass of UIScrollView, picture of a sandy beach) covered with a random number of Stone.h (subclass of UIImageView, a random PNG of a stone, userInteractionEnabled = YES to accept touches).
If the user touches and moves on the beach, it should scroll.
If the user taps a stone, it should call method "touchedStone".
If the user taps the beach where there is no stone, it should call method "touchedBeach".
Now, I realize this sounds dead simple. Everyone and everything tells me that if there's something on a UIScrollView that accepts touches that it should pass control on to it. So when I touch and drag, it should scroll; but if I tap, and it's on a stone, it should ignore beach taps and accept stone taps, yes?
However, it seems that both views are accepting the tap and calling both touchedStone AND touchedBeach. Furthermore, the beach tap occurs first, so I can't even put in a "if touchedStone then don't run touchedBeach" type flag.
Here's some code.
On BeachView.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.decelerating) { didScroll = YES; }
else { didScroll = NO; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"touched beach = %#", [touch view]);
lastTouch = touchLocation;
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
didScroll = YES;
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (didScroll == NO && isPaused == NO) {
[self touchedBeach:YES location:lastTouch];
}
[super touchesEnded:touches withEvent:event];
}
On Stone.m
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[parent stoneWasTouched]; // parent = ivar pointing from stone to beachview
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"touched stone = %#", [touch view]);
[parent touchedStone:YES location:touchLocation];
}
After a stone tap, My NSLog looks like this:
Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched Stone = <Stone: 0x1480c0>
ran touchedStone
So it's actually running both. What's even stranger is if I take the touchesBegan and touchesEnded out of Stone.m but leave userInteractionEnabled = YES, the beachView registers both touches itself, but returns the Stone as the view it touched (the second time).
Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched beach = <Stone: 0x1480c0>
ran touchedBeach
So PLEASE, I've been trying to sort this for days. How do I make it so a tapped stone calls only touchedStone and a tapped beach calls only touchedBeach? Where am I going wrong?
Is true, iPhone SDK 3.0 and up, don't pass touches to -touchesBegan: and -touchesEnded: **UIScrollview**subclass methods anymore. You can use the touchesShouldBegin and touchesShouldCancelInContentView methods that is not the same.
If you really want to get this touches, have one hack that allow this.
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;
}
This will pass to you subclass this touches, however you can't cancel the touches to UIScrollView super class.
Prior to iPhone OS 3.0, the UIScrollView's hitTest:withEvent: method always returns self so that it receives the UIEvent directly, only forwarding it to the appropriate subview if and when it determines it's not related to scrolling or zooming.
I couldn't really comment on iPhone OS 3.0 as it's under NDA, but check your "iPhone SDK Release notes for iPhone OS 3.0 beta 5" :)
If you need to target pre-3.0, you could override hitTest:withEvent: in BeachView and set a flag to ignore the next beach touch if the CGPoint is actually in a stone.
But have you tried simply moving your calls to [super touches*:withEvent:] from the end of your overridden methods to the start? This might cause the stone tap to occur first.
I had a similar problem with a simpleView and it is added to a scrollView , and whenever I touched the simpleView , the scrollView used to get the touch and instead of the simpleView , the scrollView moved . To avoid this , I disabled the srcolling of the scrollView when the user touched the simpleView and otherwise the scrolling is enabled .
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = [super hitTest:point withEvent:event] ;
if (result == simpleView)
{
scrollView.scrollEnabled = NO ;
}
else
{
scrollView.scrollEnabled = YES ;
}
return result ;
}
This could be related to a bug in iOS7 please review my issue (bug report submitted)
UIScrollView subclass has changed behavior in iOS7